PHP の JIT 実装を試す
PHP の開発者向けメーリングリストに、JIT の開発を始めたという投稿がありました。PHP 8.0 への搭載を目標として開発を進めるそうです。
php.internals: JIT for PHP project
そこで、投稿に書かれているベンチマークテストを私の環境*1でも実行してみました。結果は以下のとおりでした。それぞれ 10 回の実行時間を平均したものです。実行したベンチマークプログラムは PHP のソースコードに含まれているマイクロベンチマークです。Zend/bench.php (GitHub リポジトリ) でソースコードを確認できます。
左端の青色の系列は、次期マイナーリリースの PHP 7.1.0 RC1 で実行したものです。これは JIT なしでの実行になります。これ以外の 2 系列は JIT を有効にして実行したものです。中央の薄橙色の系列は、メーリングリストに投稿された時点でのコード*2によるもので、右端の橙色の系列は、現時点での最新版のコード*3によるものです。メーリングリストに投稿された後、いくつかのケースについて具体的な JIT 最適化が実装され、投稿時よりも高速になっているようです。
確認手順
ベンチマークテストの実行手順について説明します。まず、GitHub リポジトリからソースコードを取得します。
$ git clone -b jit-dynasm https://github.com/zendtech/php-src.git $ cd php-src
私が確認した時点でのコミット (b5fa120) にはバグがあったので*4、ビルドする前にソースコードを修正します*5。ext/opcache/jit/zend_jit_x86.dasc を開いて以下の diff の部分を修正します*6。
--- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -306,7 +306,7 @@ static int zend_jit_new(dasm_State **Dst, zend_op *opline) |.macro fp_op, fp_ins, op_type, op || if (op_type == IS_CONST) { | .if X64 -| mov aword EX->literals, r0 +| mov r0, aword EX->literals | fp_ins qword [r0 + op.constant] | .else | fp_ins qword [op.zv] @@ -345,7 +345,7 @@ static int zend_jit_new(dasm_State **Dst, zend_op *opline) |.macro sse_op, sse_ins, op_type, op, reg || if (op_type == IS_CONST) { | .if X64 -| mov aword EX->literals, r0 +| mov r0, aword EX->literals | sse_ins reg, qword [r0 + op.constant] | .else | sse_ins reg, qword [op.zv]
あとは通常どおり PHP をビルドします。configure オプションは特に指定しませんでした。
$ ./buildconf && ./configure && make
ベンチマークの実行方法は以下のとおりです。make install せずに実行しているので、opcache.so を読み込むように明示的に指定する必要があります。また、CLI で OPcache を有効にするように -dopcache.enable_cli=1 の指定も必要です。
$ for i in $(seq 1 10); do ./sapi/cli/php -dzend_extension=$(pwd)/modules/opcache.so -dopcache.enable_cli=1 -dopcache.jit_buffer_size=32M Zend/bench.php; done
1 回ごとの実行結果は以下のように出力されます。これが 10 回分続きます。
simple 0.013 simplecall 0.004 simpleucall 0.004 simpleudcall 0.004 mandel 0.022 mandel2 0.033 ackermann(7) 0.025 ary(50000) 0.004 ary2(50000) 0.003 ary3(2000) 0.058 fibo(30) 0.097 hash1(50000) 0.017 hash2(500) 0.012 heapsort(20000) 0.024 matrix(20) 0.025 nestedloop(12) 0.039 sieve(30) 0.013 strcat(200000) 0.006 ------------------------ Total 0.404
過去のコミット (2fc7a09) については、該当のコミットを checkout した後、同様にビルドして実行します。このコミットに対してはソースコードの修正は不要です。
$ git checkout 2fc7a09
PHP 7.1.0 RC1 については、以下のように圧縮ファイル*7を取得してビルド、実行しました。
$ wget https://downloads.php.net/~davey/php-7.1.0RC1.tar.bz2 $ tar xf php-7.1.0RC1.tar.bz2
[2016-09-17 追記]
最新のコードで改めて実行しました。左端は PHP 7.1.0 RC1 で中央は前回の結果 (b5fa120) です。右端が現時点での最新のコミット (359a790) によるものです。
[2016-10-10 追記]
最新のコードで実行しました。左端と中央は、PHP 7.1.0 RC1 と前回の結果 (359a790) を再掲したものです。右端が現時点での最新のコミット (75f86fe) によるものです。
[2016-11-19 追記]
10 月 26 日に開催された「第 107 回 PHP 勉強会」で LT 発表しました。JIT の開発が進むにつれてベンチマークの実行速度が向上していく様子をグラフにまとめました。
ただし、メーリングリストへの投稿によると、10/26 時点の状況としては、bench.php では 3 倍の速度向上があるものの実アプリケーションでは変わらないといったところのようです。
Now, JIT passes almost all PHPT tests, makes 3 times speed-up on bench.php and no significant difference on real-life apps (+/-5% depended on opcache.jit setting.
http://news.php.net/php.internals/96613
*1:Lenovo ThinkPad Edge E130, Core i5-3337U 1.8GHz で、Windows 7 上の仮想機械としてインストールした CentOS 7 を利用しています。
*2:https://github.com/zendtech/php-src/tree/2fc7a09
*3:https://github.com/zendtech/php-src/tree/b5fa120。ただし、このコミットに対して後述の修正が必要でした。
*4:[2016-09-17 追記] 既に修正されています。
*5:JIT が生成するコードの誤りのため、修正しなくてもビルドは成功し、実行時に segmentation fault になります。
*6:定数リテラルのテーブルのベースアドレスをレジスタ r0 に読み込む処理のようですが、オペランドの順序が逆になっています。