PHP8.0.0α1がリリースされたのでさっそくJITの威力を体感する(した)

2020/06/25、[PHP8.0.0α1がリリースされました](https://www.php.net/archive/2020.php#2020-06-25-1)。
PHP8系の初のバージョンです。
ただし名前のとおりα版であり、まだまだ実環境で使えるものではありません。
今後は2020/08/04にフィーチャーフリーズ、即ち新機能の取り込みが終了し、その後はβやRCで徐々に完成度を高めながら、2020/11/26に正式版がリリースされる予定です。

そんなわけでPHP8の目玉、[JIT](https://qiita.com/rana_kualu/items/ba312d2789bd228f887a)の性能を試してみることにしましょう。

今回はXAMPPに導入してWebサーバとして動かすことができなかったので、以下のベンチマークはコマンドラインで実行した結果となります。
きっとそのうちXAMPPも対応してくれるはず。


# インストール

[QA Releases](https://windows.php.net/qa/)からVS16 x64 Thread Safeのzipをダウンロード。
適当なディレクトリに解凍。
`php.ini-development`を`php.ini`にコピー。

php.iniを変更。
・`memory_limit = 1024M`にする
・`date.timezone = "Asia.Tokyo"`にする
・`extension_dir = "ext"`のコメントアウトを外す


# ベンチマークの設定

`デフォルト`の設定
・上の『php.iniを変更』のまま。ほぼ初期状態。

`opcache有効`の設定
・『`デフォルト`の設定』に対して、以下を追加する。
・`zend_extension=opcache`
・`opcache.enable=1`
・`opcache.enable_cli=1`
・`opcache.optimization_level=0x7FFFBFFF`

`JIT有効`の設定
・『`opcache有効`の設定』に対して、以下を追加する。
・`opcache.jit_buffer_size = 128M`


# ベンチマーク結果

プログラムは最後に載せておきますが、[JITのRFC](https://wiki.php.net/rfc/jit)で使われていたマンデルブロ集合を計算するやつです。
使用したCPUはIntel i7-9700 3.00GHz。
測定結果の単位は秒です。

| デフォルト | opcache有効 | JIT有効 | 参考7.4.7 |
|---|---|---|---|
| 0.814755 | 0.355585 | 0.106190 | 0.960383 |
| 0.818260 | 0.356907 | 0.106928 | 0.938955 |
| 0.822746 | 0.353799 | 0.106061 | 0.940920 |
| 0.817202 | 0.353423 | 0.106768 | 0.951347 |
| 0.819391 | 0.353574 | 0.106117 | 0.936791 |


本当かよ？？？？？？？？？

まずPHP7.4.7からPHP8にアップデートするだけで処理時間が1割削減されています。
ただでさえ[新機能てんこ盛り](https://qiita.com/tags/php8)だってのに、そのうえ速度も上がるとかどうなってるんだPHP8。

次いでopcacheを有効にすると処理時間が半分になります。

最後にJITを有効にしたら、処理時間がopcache有効状態の30%になりました。
30%縮まりました、ではありません。
なんだこれ。

ということで、JITを有効にするだけで、処理時間がPHP8デフォルト設定の13%になりました。
どういうことかというと、元々1分かかっていた処理が8秒で終わるようになります。
足枷を外したとかいうレベルじゃねーぞ。
これ本当に計算してるのか？
計算結果が固定値だから結果だけどこかに保存してるとかじゃないよな？

しかもこれ、opcacheやJITの設定はほぼ初期値で、とりあえず有効にしただけという状態ですからね。
チューニングすればさらに早くなることでしょう。
面倒なので今回はそこまでやってませんが。


# プリロード

PHP7.4.0で入った[プリロード](https://qiita.com/rana_kualu/items/afe544b0f5680e81fabc)で更なる高速化を図ってみましょう。

・`opcache.preload="path/to/preload.php"`を設定
・`preload.php`の中身は`opcache_compile_file('path/to/mandelbrot.php');`

> Error Preloading is not supported on Windows

はい。

実はWindowsではプリロードを使えません。

PHP7.4.0リリース時点では[Windowsでもプリロードを使えていた](https://qiita.com/rana_kualu/items/50f9f5735321fe995ff5#%E3%83%97%E3%83%AA%E3%83%AD%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0)のですが、その後[PHP7.4.2で提供が中止](https://www.php.net/ChangeLog-7.php#7.4.2)されてしまいました。
[実はWindowsのプリロードはASLRのせいで本当のプリロードじゃないんだ](https://github.com/php/php-src/pull/4999)とかいうことらしいですがよくわかりません。

そんなわけで*nix勢あとよろ。


# 感想

あくまでCPUだけをがりがり使うプログラムに対しての検証結果です。
ファイルやデータベースへのアクセスが多くなる一般的なプログラムについては、また異なる結果になるでしょう。
またLaravelなど普通のWebアプリがどの程度高速化されるかも、今回は調べられていません。

とはいえ、そうはいってもさすがにこの結果は驚異的です。
もはや環境を作れないので試していませんが、[PHP5.6からPHP7で速度が倍以上になった](https://qiita.com/rana_kualu/items/05a510f53c0ea9a1ccaf)という過去もあります。
全部合わせるとPHP8が処理にかかる時間はPHP5の5%とかです。
PHP8はもはや、PHP5時代とは別次元の速度を手にいれました。


# ベンチマークプログラム

[マンデルブロ集合を計算するやつ](https://gist.github.com/dstogov/12323ad13d3240aee8f1)のほぼコピペです。

実行はコマンドラインから`path/to/php8/php.exe path/to/mandelbrot.php`とするだけ。

```php:mandelbrot.php
define("BAILOUT", 16);
define("MAX_ITERATIONS", 5000); // 1000だと早すぎたので

class Mandelbrot
{
    public function __construct()
    {
        $output = '';
        $d1 = microtime(1);
        for ($y = -39; $y < 39; $y++) {
            for ($x = -39; $x < 39; $x++) {
                if ($this->iterate($x/40.0, $y/40.0) == 0) {
                    $output .= '*';
                } else {
                    $output .= ' ';
                }
            }
            $output .= "\n";
        }
        $d2 = microtime(1);
        $diff = $d2 - $d1;
        echo $output; // 出力は最後にまとめた
        printf("\nPHP Elapsed %0.6f\n", $diff);
    }

    public function iterate($x, $y)
    {
        $cr = $y-0.5;
        $ci = $x;
        $zr = 0.0;
        $zi = 0.0;
        $i = 0;
        while (true) {
            $i++;
            $temp = $zr * $zi;
            $zr2 = $zr * $zr;
            $zi2 = $zi * $zi;
            $zr = $zr2 - $zi2 + $cr;
            $zi = $temp + $temp + $ci;
            if ($zi2 + $zr2 > BAILOUT) {
                return $i;
            }
            if ($i > MAX_ITERATIONS) {
                return 0;
            }
        }
    }
}

$m = new Mandelbrot();
```