y_uti のブログ

統計、機械学習、自然言語処理などに興味を持つエンジニアの技術ブログです

PHP の JIT 実装を試す

PHP の開発者向けメーリングリストに、JIT の開発を始めたという投稿がありました。PHP 8.0 への搭載を目標として開発を進めるそうです。
php.internals: JIT for PHP project

そこで、投稿に書かれているベンチマークテストを私の環境*1でも実行してみました。結果は以下のとおりでした。それぞれ 10 回の実行時間を平均したものです。実行したベンチマークプログラムは PHPソースコードに含まれているマイクロベンチマークです。Zend/bench.php (GitHub リポジトリ) でソースコードを確認できます。
f:id:y_uti:20160904100214p:plain

左端の青色の系列は、次期マイナーリリースの 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) によるものです。
f:id:y_uti:20160917134859p:plain

[2016-10-10 追記]
最新のコードで実行しました。左端と中央は、PHP 7.1.0 RC1 と前回の結果 (359a790) を再掲したものです。右端が現時点での最新のコミット (75f86fe) によるものです。
f:id:y_uti:20161010123900p:plain

[2016-11-19 追記]
10 月 26 日に開催された「第 107 回 PHP 勉強会」で LT 発表しました。JIT の開発が進むにつれてベンチマークの実行速度が向上していく様子をグラフにまとめました。

www.slideshare.net

ただし、メーリングリストへの投稿によると、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 に読み込む処理のようですが、オペランドの順序が逆になっています。

*7:これは php-build の定義ファイルに指定されているものです。