PHP 7.1でOPcacheがすごい速くなってた

去る12/1（日本時間12/2）にPHP 7.1.0が無事リリースされましたね！みなさんビルドしてみましたか？

そんな中、PHP Classesの記事「[PHP Performance Evolution 2016 from PHP 5, PHP 7.0, PHP 7.1 and PHP 8/Next](https://www.phpclasses.org/blog/post/493-php-performance-evolution.html)」にPHP 7.0から7.1で結構な性能改善があったと書いてあったので、追試してみたら本当だったという話を紹介します。

# 記事の概要

上記の記事ではPHP 5.6→7.0での性能改善が大きかったこと、7.0→7.1でもかなりの性能改善があったこと、PHP 8.0になるであろうJITコンパイル実装も現時点でかなり速いことなどが紹介されています。

このうち1番目と3番目は既に知られている内容だと思いますが、2番目は筆者は初耳だったので、手元の環境で確かめてみました。

# ベンチマーク（OPcacheなし）

手元の環境はMac OS X 10.11(El Capitan)、PHP 7.0.13およびPHP 7.1.0はphp-buildで自前コンパイルしたものです。

PHPソースコードに付属の`bench.php`で実験してみたところ、次のような結果が得られました。

|                    | PHP 7.0.13 | PHP 7.1.0 |
| ------------------ | ---------: | --------: |
| simple             |      0.034 |     0.035 |
| simplecall         |      0.011 |     0.011 |
| simpleucall        |      0.035 |     0.035 |
| simpleudcall       |      0.038 |     0.037 |
| mandel             |      0.139 |     0.137 |
| mandel2            |      0.146 |     0.142 |
| ackermann(7)       |      0.034 |     0.032 |
| ary(50000)         |      0.009 |     0.008 |
| ary2(50000)        |      0.005 |     0.007 |
| ary3(2000)         |      0.081 |     0.069 |
| fibo(30)           |      0.129 |     0.122 |
| hash1(50000)       |      0.013 |     0.012 |
| hash2(500)         |      0.012 |     0.011 |
| heapsort(20000)    |      0.040 |     0.035 |
| matrix(20)         |      0.037 |     0.032 |
| nestedloop(12)     |      0.068 |     0.068 |
| sieve(30)          |      0.022 |     0.024 |
| strcat(200000)     |      0.006 |     0.006 |
| **Total**          |  **0.861** | **0.822** |

確かにPHP 7.1.0の方が速いものの、テストによってはほとんど差が見られず、全体で見ると5%程度の改善にとどまっていることがわかります。これだけ見ると、性能改善は一休みかな？と思ってしまいそうですし、筆者もそういう印象を持っていました。

# ベンチマーク（OPcacheあり）

ところが、上の記事によればPHP 7.1.0ではOPcacheの性能改善が大きかったようです。CLI版ではOPcacheはデフォルトで無効なので、PHPのコマンドラインオプションに`-dopcache.enable_cli=1`をつけて、OPcacheありのときの性能を比較してみたのが以下の表です。

|                    | PHP 7.0.13 | PHP 7.1.0 |
| ------------------ | ---------: | --------: |
| simple             |      0.025 |     0.018 |
| simplecall         |      0.008 |     0.007 |
| simpleucall        |      0.033 |     0.007 |
| simpleudcall       |      0.032 |     0.009 |
| mandel             |      0.121 |     0.053 |
| mandel2            |      0.138 |     0.076 |
| ackermann(7)       |      0.030 |     0.028 |
| ary(50000)         |      0.008 |     0.006 |
| ary2(50000)        |      0.006 |     0.007 |
| ary3(2000)         |      0.076 |     0.060 |
| fibo(30)           |      0.102 |     0.096 |
| hash1(50000)       |      0.013 |     0.012 |
| hash2(500)         |      0.012 |     0.012 |
| heapsort(20000)    |      0.036 |     0.029 |
| matrix(20)         |      0.034 |     0.029 |
| nestedloop(12)     |      0.048 |     0.043 |
| sieve(30)          |      0.024 |     0.014 |
| strcat(200000)     |      0.006 |     0.006 |
| **Total**          |  **0.753** | **0.512** |

OPcacheを有効にしてみると、両者の差は歴然です。全体で見ると30%の改善、テストによっては倍以上の性能になっているものもチラホラあります。

# opcodeを確認してみた

PHP 7.1.0のOPcache最適化後のopcodeを確認してみたところ、たとえば`simpleucall()`などは不要な関数呼び出しをばっさり削っており、通常のアプリケーションではあまり参考にならない結果だと感じました。

```php
function hallo($a) {
}

function simpleucall() {
  for ($i = 0; $i < 1000000; $i++)
    hallo("hallo");
}
```

```
$ phpdbg -dopcache.enable_cli=1 -p'simpleucall' Zend/bench.php
function name: simpleucall
L28-31 simpleucall() /Users/hnw/src/php/php-7.1.0/Zend/bench.php - 0x10d2be240 + 6 ops
 L29   #0     QM_ASSIGN               0                                         $i
 L29   #1     JMP                     J3
 L29   #2     PRE_INC                 $i
 L29   #3     IS_SMALLER              $i                   1000000              ~0
 L29   #4     JMPNZ                   ~0                   J2
 L31   #5     RETURN                  null
[Script ended normally]
```

たしかに、このコードなら関数呼び出しを削っても挙動は変わりません。どうせならループごと削れるのでは？という気もしますが、ループは律儀に回してくれるようです。

一方で、たとえば`sieve()`（エラトステネスのふるい）が高速になった理由はパッと見ではわかりませんでした。

このPHP 7.1.0でのOPcacheの性能改善が実アプリケーションでも有効かどうかはまだわかりませんが、もし高速になるなら嬉しいニュースだと言えそうです。続報が楽しみですね。