PHP7.4でFFI(Foreign Function Interface)が使えるようになります。というわけで遊びましょう。
FFIとは?
要するに.dll
や.so
の共有ライブラリをロードして、PHPからCの関数をコールしたり、構造体いじったり出来ます。ネイティブ実行なので処理速度が速いという期待があります。さて、どれくらい速いのか?わざとらしいサンプルで検証してみます。
モンテカルロ法
ランダムな0~1の任意の数値を利用して円周率を計算する方法です。試行回数が多ければ多いほど精度が上がるのですが、実行時間が長くなります。
PHPのソース
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
define('RAND_MAX', 2147483647);
function calcPi(int $n) :float { $count = 0; for($i=0; $i<$n ;$i++){ $x = rand(0,RAND_MAX)/RAND_MAX; $y = rand(0,RAND_MAX)/RAND_MAX; if($x*$x + $y*$y <= 1){ $count++; } } return 4*$count/$n; } echo sprintf("PI => %f\n", calcPi(10000000));
|
RAND_MAX
はC言語側のソースコードと精度を合わせたいので定義してます。
実行時間は
1 2 3 4 5 6
| $ time php monte.php PIE => 3.141581
real 0m5.882s user 0m5.824s sys 0m0.061s
|
C言語のソース
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <stdio.h> #include <stdlib.h> #include <time.h>
double calcPi(int n);
int main() { printf("PI = %f", calcPi(10000000)); return(0); }
double calcPi(int n){ int count; double x,y; srand(time(NULL)); count = 0; for(int i = 0; i < n; ++i) { x = (double)rand() / RAND_MAX; y = (double)rand() / RAND_MAX; if( (x * x + y * y) <= 1 ) { count++; } } return (double) count / n * 4; }
|
実行時間は
1 2 3 4 5 6
| $ gcc monte.c -o monte $ time ./monte PI = 3.141165 real 0m0.377s user 0m0.372s sys 0m0.004s
|
やはり、ネイティブ実行は速い!というわけで、今度はFFIを使ってPHPからCのライブラリを呼び出して見ましょう。
PHPでモンテカルロ法の計算を行うのではなく、計算処理をC言語のネイティブ実行に任せることで処理速度の向上が見込めるはずです。
FFIを使ったモンテカルロ法
まずは、共有ライブラリを作ります。ソースコードは先程のC言語のコードとほぼ変わりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <stdio.h> #include <stdlib.h> #include <time.h>
double calcPi(int n);
double calcPi(int n){ int count; double x,y; srand(time(NULL)); count = 0; for(int i = 0; i < n; ++i) { x = (double)rand() / RAND_MAX; y = (double)rand() / RAND_MAX; if( (x * x + y * y) <= 1 ) { count++; } } return (double) count / n * 4; }
|
それっぽい雰囲気を出すために、libmonte.c
という名前で保存します。
共有ライブラリとしてコンパイルして、libmonte.so
を作成。
1
| $ gcc -fPIC -shared -o libmonte.so libmonte.c
|
FFIを使って、libmonte.so
のcalcPi
関数を呼び出します。かなりシンプルに書けますね。
1 2 3
| <?php $ffi = FFI::cdef( "double calcPi(int n);", __DIR__."/libmonte.so"); echo sprintf("PI => %f\n", $ffi->calcPi(10000000));
|
実行時間は…
1 2 3 4 5 6
| $ time php ffi.php PI => 3.142153
real 0m0.466s user 0m0.413s sys 0m0.058s
|
いやー、わざとらしく速くなりました。めでたしめでたし。FFI初めて使ってみましたが、書き方もシンプルだし良いですね!
まとめ
というわけで、FFIを使うことで時間のかかる計算処理が大分速く実行できるというサンプルでした。この他にも色んな使いみちがありそうです。
次は、FFIがPHPのソースコード内でどのような動作をしているのかを追ってみようと思います。