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
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
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 | #include <stdio.h> |
それっぽい雰囲気を出すために、libmonte.c
という名前で保存します。
共有ライブラリとしてコンパイルして、libmonte.so
を作成。1
$ gcc -fPIC -shared -o libmonte.so libmonte.c
FFIを使って、libmonte.so
のcalcPi
関数を呼び出します。かなりシンプルに書けますね。1
2
3
$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のソースコード内でどのような動作をしているのかを追ってみようと思います。