【PHP8】JIT=on の Docker を作ってみた。触ってみたら爆速だったよ。

> [PHP8 で JIT が使えるようになる](https://qiita.com/rana_kualu/items/ba312d2789bd228f887a)そうなので、ワクワクが止まりません。手軽に触れるイメージが Docker ないものか。

- [2020/06/23 に PHP8-alpha1 がリリースされた](https://github.com/php/php-src/releases/tag/php-8.0.0alpha1)ので、いよいよリリース間近になりました。楽しみです。

## TL; DR

[PHP のソースコードの `master`](https://github.com/php/php-src)からコンパイルして JIT を有効にした Docker イメージを作ってみました。Mac に**標準インストールされている PHP7.1 と比較して 2〜5 倍近く速い**です。

信じられないかもしれませんが、macOS に標準インストールされている PHP v7.3.11 をローカルで動かすより、ラズパイ3 の Docker 上で動かした方が JIT が有効になっていなくても速いんです。（詳しくは TS; DR）

> <sub><sup>🐒 &nbsp; 2021/03/21 追記: 公式の PHP8 の Docker イメージもラズパイ（ARM 系 CPU）でも動くようになりました。しかし、当イメージおよび公式も、**残念ながらラズパイ上では JIT を有効にできません**。ARM 系 CPU 用の JIT の PHP モジュールがまだ完成していないためです。それでも PHP 7 系より速いですが。</sup></sub>

```bash
docker pull keinos/php8-jit:latest
```

<a href="https://github.com/KEINOS/Dockerfile_of_PHP8-JIT"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/KEINOS/Dockerfile_of_PHP8-JIT.png" width="460px"></a>
[![](https://img.shields.io/docker/image-size/keinos/php8-jit?sort=semver)](https://cloud.docker.com/repository/docker/keinos/php8-jit "Docker Image Size (latest semver)") ![Docker Pulls](https://img.shields.io/docker/pulls/keinos/php8-jit) [![](https://img.shields.io/docker/v/keinos/php8-jit)](https://hub.docker.com/r/keinos/php8-jit/tags "ビルド日")

- このイメージは、Mac や Windows の Intel/AMD の x86_64 系以外にも、**ラズパイ3、ラズパイZero の Docker でも動く**ようにしています。
    - 動作確認 OS とハード:
        - macOS Mojave (MacBookPro Early 2015, x86_64）
        - Raspbian Stretch (RaspberryPi 3, ARMv7l）
        - Raspbian Buster (RaspberryPi ZeroW, ARMv6l）
        - いずれも Docker v19.03.2
- このイメージは、`JIT` を有効にしていますが、ARM 系 CPU の場合、無効になってしまいます。
- このイメージは、`mbstring` を有効、`language` を `japanese`、タイムゾーンを `Asia/Tokyo` に設定してあります。
- Docker イメージ: https://hub.docker.com/r/keinos/php8-jit @ Docker Hub
- Dockerfile: https://github.com/KEINOS/Dockerfile-of-PHP8-JIT @ GitHub
- 拡張モジュール:
    - [ロードされているモジュール一覧](https://github.com/KEINOS/Dockerfile_of_PHP8-JIT/blob/php8-jit/info-get_loaded_extensions.txt)
    - 拡張モジュールのインストール:

        ```bash:"sockets"モジュールをインストールする例
        docker-php-ext-install sockets
        ```

- PHP Info:
    - [phpinfo() の内容](https://github.com/KEINOS/Dockerfile_of_PHP8-JIT/blob/php8-jit/info-phpinfo.txt)
- Reference:
    - [PHP GR8](https://blog.krakjoe.ninja/2019/03/php-gr8.html) | Musings, ninja ones @ Joe Watkins' Blog
    - 下記記事とイメージが大変参考になりました。
        - [Compiling PHP 8 from source with JIT support](https://arkadiuszkondas.com/compiling-php-8-from-source-with-jit-support/) @ arkadiuszkondas.com
        - [How to run PHP 8 with JIT support using Docker](https://arkadiuszkondas.com/how-to-run-php-8-with-jit-support-using-docker/) @ arkadiuszkondas.com

### 簡易速度テスト

Test                    | v5.6.40 | v7.0.33 | v7.1.33 | v7.2.31 | v7.3.18 | v7.4.6 | 8.0.0-dev<br>(JIT Off) | 8.0.0-dev<br>(JIT On)
:---------------------- | :-----: | :-----: | :-----: | :-----: | :-----: | :----: | :---: | :--: |
[Fibonacci(32)](https://github.com/KEINOS/Dockerfile-of-PHP8-JIT/blob/php8-jit/test/test-fibonacci.php)         | 1.521   | 0.665   | 0.598   | 0.269   | 0.239   | 0.194  | 0.261 | 0.107
[Zundoko-Kiyoshi Looping](https://github.com/KEINOS/Dockerfile-of-PHP8-JIT/blob/php8-jit/test/test-zundoko.php) | 2.485   | 1.462   | 1.413   | 0.701   | 0.646   | 0.636  | 0.672 | 0.416
-- [Zend Bench](https://github.com/KEINOS/Dockerfile-of-PHP8-JIT/blob/php8-jit/test/test-zend_bench.php) --     |         |         |         |         |         |        |       |
simple                  | 0.178   | 0.100   | 0.101   | 0.064   | 0.051   | 0.041  | 0.054 | 0.002
simplecall              | 0.186   | 0.027   | 0.027   | 0.010   | 0.010   | 0.007  | 0.010 | 0.001
simpleucall             | 0.210   | 0.071   | 0.080   | 0.023   | 0.018   | 0.025  | 0.022 | 0.001
simpleudcall            | 0.226   | 0.076   | 0.088   | 0.028   | 0.021   | 0.021  | 0.024 | 0.001
mandel                  | 0.491   | 0.320   | 0.329   | 0.189   | 0.190   | 0.175  | 0.189 | 0.007
mandel2                 | 0.643   | 0.360   | 0.358   | 0.167   | 0.167   | 0.184  | 0.170 | 0.008
ackermann(7)            | 0.187   | 0.061   | 0.067   | 0.032   | 0.031   | 0.031  | 0.033 | 0.015
ary(50000)              | 0.031   | 0.008   | 0.007   | 0.007   | 0.008   | 0.007  | 0.007 | 0.007
ary2(50000)             | 0.025   | 0.006   | 0.005   | 0.006   | 0.007   | 0.006  | 0.006 | 0.006
ary3(2000)              | 0.298   | 0.142   | 0.124   | 0.059   | 0.047   | 0.044  | 0.049 | 0.015
fibo(30)                | 0.594   | 0.230   | 0.228   | 0.106   | 0.091   | 0.081  | 0.093 | 0.042
hash1(50000)            | 0.049   | 0.024   | 0.024   | 0.016   | 0.015   | 0.015  | 0.015 | 0.016
hash2(500)              | 0.053   | 0.022   | 0.023   | 0.013   | 0.008   | 0.008  | 0.008 | 0.011
heapsort(20000)         | 0.145   | 0.073   | 0.069   | 0.037   | 0.036   | 0.036  | 0.037 | 0.014
matrix(20)              | 0.130   | 0.067   | 0.062   | 0.034   | 0.035   | 0.030  | 0.030 | 0.014
nestedloop(12)          | 0.301   | 0.145   | 0.143   | 0.088   | 0.091   | 0.072  | 0.091 | 0.013
sieve(30)               | 0.151   | 0.041   | 0.053   | 0.021   | 0.018   | 0.014  | 0.017 | 0.005
strcat(200000)          | 0.027   | 0.014   | 0.015   | 0.011   | 0.011   | 0.011  | 0.010 | 0.010
---------------         | ------- | ------- | ------- | ------- | ------- | ------ | ----- | -----
Total                   | 3.923   | 1.787   | 1.804   | 0.911   | 0.855   | 0.805  | 0.867 | 0.187


### 使い方

<details><summary>`docker run --rm -it keinos/php8-jit:latest`</summary><div><br>

<h4>基本構文</h4>

```bash
docker run --rm -it keinos/php8-jit:latest [コマンド] [引数]
```
```bash:コマンド実行例
docker run --rm -it keinos/php8-jit:latest php -a
```

- `docker run` で「コンテナの起動」
- `--rm` で「終了後にコンテナを破棄（`remove`）」（ロングオプションなので `--` です）
- `-it` でシェル操作をコンテナとつなげる。
    - `-i -t` もしくはロングオプションの `--interactive --tty` と同じ意味です。つまり、コンテナの標準入出力とローカルの標準入出力を [tty](https://ja.wikipedia.org/wiki/Tty) 経由でつなげ、コンテナのシェル操作をローカルのシェルから行えるようになります。これを忘れると、実行コマンド終了後に入力待ちにならず処理が終わりコンテナがシャットダウンしてしまいます。
- `keinos/php8-jit:latest` で「コンテナの元となるイメージを指定」
- `php` でランタイムを指定
- `-a` で

<h4>対話モードで実行</h4>

```shell-session:PHP対話モードで実行
$ # コマンドや引数の指定がない場合は対話モードで起動します（デフォルトの動作）
$ docker run --rm -it keinos/php8-jit:latest
Interactive shell

php > echo phpversion();
8.0.0-dev
php > exit
$
$ # コマンドが php -a の場合と同じ動作です。
$ docker run --rm -it keinos/php8-jit:latest php -a
Interactive shell

php > echo phpversion();
8.0.0-dev
php > exit
$
```

<h4>PHP コマンドの直接実行</h4>

```shell-session:PHPコマンドを直接実行
$ # php --version の実行
$ docker run --rm -it keinos/php8-jit:latest php --version
PHP 8.0.0-dev (cli) (built: Jul 19 2019 07:09:36) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0-dev, Copyright (c), by Zend Technologies
$
$ # php -r で `echo "hoge", PHP_EOL;` のスクリプトを実行
$ docker run --rm -it keinos/php8-jit:latest php -r 'echo "hoge", PHP_EOL;'
hoge
$
```

<h4>コンテナ内から PHP を実行</h4>

```shell-session:コンテナ内のシェルから実行
$ # /bin/sh でコンテナ内の Bourne Shell(sh)シェルを起動
$ docker run --rm -it keinos/php8-jit:latest /bin/sh
/ $ # コンテナ内の sh シェルから php -v を実行
/ $ php -v
PHP 8.0.0-dev (cli) (built: Jul 19 2019 07:09:36) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0-dev, Copyright (c), by Zend Technologies
/ $ 
/ $ # コンテナ内の sh シェルから`echo "hoge", PHP_EOL;` のスクリプトを php -r で実行
/ $ php -r 'echo "hoge", PHP_EOL;'
hoge
/ $ # シェルを抜けてコンテナを終了
/ $ exit
$
```

<h4>コンテナにファイルをマウントして実行</h4>

```shell-session:ファイルをマウントして実行
$ # ローカルのファイルを用意
$ ls
test.php
$ cat test.php
<?php

echo 'Hello World!', PHP_EOL;

$ # test.php をマウントして実行
$ docker run --rm \
>   -v $(pwd)/test.php:/app/test.php \
>   keinos/php8-jit:latest \
>   php /app/test.php
Hello World!
$
```

<h4>コンテナにディレクトリをマウントして実行</h4>

```shell-session:ディレクトリをマウントして実行
$ # ローカルのディレクトリを確認
$ ls
src
$ ls src
index.php     functions.inc.php
$ # src ディレクトリをマウントして実行
$ docker run --rm \
>   -v $(pwd)/src:/app \
>   -w /app \
>   keinos/php8-jit:latest \
>   php index.php
Hello World! from a function.
$
```

</div></details>

### ビルド

Docker Hub から Pull せずにイメージをビルドしたい場合は、リポジトリを `clone` してビルドしてください。

```shell-session
$ # 適当な作業ディレクトリに移動
$ cd ~/
$ # リポジトリを clone
$ git clone https://github.com/KEINOS/Dockerfile-of-PHP8-JIT.git php8
$ # リポジトリに移動
$ cd $_
$ # Docker イメージのビルド
$ docker build --no-cache -t php8-jit .
...
```

## TS;DR（リリースまで JIT 我慢できない）

- [【PHP8】PHPでJITが使えるようになる](https://qiita.com/rana_kualu/items/ba312d2789bd228f887a) @ Qiita

上記の Qiita 記事を読んでワクワクが止まらず、触ってみたい欲にかられました。

非公式ながらも [PHP 8.0.0 Dev 版の Docker イメージはある](https://hub.docker.com/r/akondas/php)のですが、残念ながらデフォルトで JIT が有効になっておらず、`mb_string` なども一緒にコンパイルされていません。

また、**PHP 8.0.0 とは言っても最新の `master` ブランチを使っているので一番 PHP8 に近いバージョンというだけ**のようですが、それでも一応 [`master` は PHP8 用として進められている](https://github.com/php/php-src/blob/master/UPGRADING)ようです。

[JIT の正式採用は PHP8 からと決定された](https://blog.krakjoe.ninja/2019/03/php-gr8.html)ので、この Docker イメージは「[いちはやく JIT の挙動をみるための目的で作成された](https://arkadiuszkondas.com/compiling-php-8-from-source-with-jit-support/)」とのこと。どうやら JIT を有効にした場合に PHP のコンパイルに難があるようで、それらの注意点をまとめた記事をベースに作られたそうです。

- [Compiling PHP 8 from source with JIT support](https://arkadiuszkondas.com/compiling-php-8-from-source-with-jit-support/) @ Arkadius Zkondas' Blog

### 測定

そこで、上記記事を参考に JIT および `mb_string` を有効にした [PHP8 の Dockerfile を作ってみた](https://github.com/KEINOS/Dockerfile-of-PHP8-JIT)ので、測定してみました。自分でコンパイルしてみたいかたは、[リポジトリ](https://github.com/KEINOS/Dockerfile-of-PHP8-JIT)を `clone` して、`docker build` してみてください。

測定は「[フィボナッチ数](https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0)」の算出と「[ズンドコキヨシ](https://qiita.com/mpyw/items/b9be5c81ef9389c0267d)」ループです。リポジトリには Zend の ベンチマークも置いてあります。

#### テストコード

- https://github.com/KEINOS/Dockerfile-of-PHP8-JIT/tree/php8-jit/test @ GitHub

#### 比較用スクリプト

- https://github.com/KEINOS/Dockerfile-of-PHP8-JIT/blob/php8-jit/run-benchmark.sh

#### 実行結果

> - 同じ PHP8 同士でも JIT を有効にするだけで 2〜3 倍近く速なった。
> - Docker で PHP を使うなら 7.3.6 がバランス的（速度・安定的に）にベストと思われる。

| PHPバージョン| 実行環境 | 実測値（フィボナッチ数）　|
| :--- |:---:| :--- |
| PHP 5.6.40 | Docker | 1.4402780532837 |
| PHP 7.1.23 | ローカル実行 | <font color=red>0.3146538734436</font> |
| PHP 7.1.23 | Docker | 0.57761478424072 |
| PHP 7.3.6  | Docker | 0.23189496994019 |
| PHP 8.0.0-dev | Docker<br>(JIT=off）| 0.24225091934204 | 
| PHP 8.0.0-dev | Docker<br>(JIT=on) | <font color=blue>0.093664884567261</font> | 


| PHPバージョン | 実行環境 | 実測値（ズンドコキヨシ）　|
| :--- | :--: |:--- | :--- |
| php 5.6.40 | Docker | 2.3430778980255 |
| PHP 7.1.23 | ローカル実行 | <font color=red>0.75591492652893</font> |
| php 7.1.23 | Docker | 1.3070819377899 |
| php 7.3.6 | Docker | 0.60567092895508 |
| php 8.0.0-dev | Docker<br>(JIT=off）| 0.6335301399231 | 
| php 8.0.0-dev | Docker<br>(JIT=on）| <font color=blue>0.39926886558533</font> | 

- 検証結果
  - macOS のデフォルト PHP 7.1.23 のローカル実行と比較しても 2〜3 倍速い。
  - JIT を有効にしない場合は PHP8 on Docker がローカル実行の PHP7.1.23 よりやや速いくらい。
  - v7.1.23 と v7.3.6 では、v7.3.6 の方がローカルより速い。
- 検証環境
  - マシン: MacBook Pro, 2.7 GHz IntelCore, 13inch Early 2015
  - OS: macOS Mojave (OSX 10.14.5)
  - Docker: 19.03.2