この記事は GRIPHONE Advent Calendar 2020 11日目の記事です。
サーバーサイドエンジニアのtackです。
最近リリースされたPHP8が爆速ということで、PHP7からPHP8にするとどれぐらい実行速度が早くなるのか、また、移行はどれぐらい大変なのかをStudio MGCMより提供されているマジカミを使って試してみようと思います。
サーバー環境
現在使われているサーバー環境は以下の様になっています。
- PHP7系
- CodeIgniter3
- MySQL
- Redis
これをPHP8.0にアップデートして、パフォーマンスを比較していきます。
PHP 8.0.0 (cli) (built: Nov 24 2020 17:04:03) ( NTS gcc x86_64 )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
with Zend OPcache v8.0.0, Copyright (c), by Zend Technologies
PHPRedisは最新バージョンの5.3.2がPHP8.0に対応しているのでpeclでインストールするだけです!
JITの有効化
PHP8はデフォルト設定ではJITによる高速化が有効になっていないので、php.iniなどの設定ファイル内にあるopcacheの項目に以下を追加します。
opcache.jit_buffer_size=32M
php -i
コマンドでopcache.jit*
の項目が以下の様になっていればOKです。
opcache.jit => tracing => tracing
opcache.jit_bisect_limit => 0 => 0
opcache.jit_blacklist_root_trace => 16 => 16
opcache.jit_blacklist_side_trace => 8 => 8
opcache.jit_buffer_size => 32M => 32M
opcache.jit_debug => 0 => 0
opcache.jit_hot_func => 127 => 127
opcache.jit_hot_loop => 64 => 64
opcache.jit_hot_return => 8 => 8
opcache.jit_hot_side_exit => 8 => 8
opcache.jit_max_exit_counters => 8192 => 8192
opcache.jit_max_loop_unrolls => 8 => 8
opcache.jit_max_polymorphic_calls => 2 => 2
opcache.jit_max_recursive_calls => 2 => 2
opcache.jit_max_recursive_returns => 2 => 2
opcache.jit_max_root_traces => 1024 => 1024
opcache.jit_max_side_traces => 128 => 128
opcache.jit_prof_threshold => 0.005 => 0.005
コードのバージョンアップ
PHP8でDeprecatedになった機能や、言語仕様が変わったものがあるので修正していきます。
マジカミの場合は使っているライブラリー内で以下の様なエラーが起こったので修正します。
Deprecated対策
each関数などPHP8.0で非推奨になった機能がいくつかあります。
とりあえず、今回はパフォーマンステストが目的で、Deprecatedで止ってしまうのは面倒なので、php.iniでエラーレベルを変更しちゃいます。
error_reportingに~E_DEPRECATED
を追加します。
error_reporting = E_ALL & ~E_DEPRECATED
名前付き引数
PHP8から名前付き引数が使えるようになりました。それにより、call_user_func_arrayの仕様も変わったようで、2番目の引数$param_arrに連想配列を渡すと、キーを名前付き引数として扱うようです。
function foo(int $a, int $b)
{
echo "a={$a}, b={$b}\n";
}
$param_arr = [
'b' => 5,
'a' => 10,
];
call_user_func_array('foo', $param_arr);
// a=10, b=5
// と表示される
PHP7系では連想配列のキーは無視されていたので、挙動が変わってしまいます。$param_arrに渡す前にarray_values関数などを使って値だけの配列にしましょう。
パフォーマンス比較
結果から載せちゃいます。
サーバー側で計測した各APIの処理時間の比較を以下の表にまとめました。
PHPの処理が多い場合はPHP8にした効果が大きく、かなり高速になっています。それ以外のDBやRedisへのアクセスが多い処理に関しても実行時間が早くなっています。
処理 | PHP7(ms) | PHP8(ms) | 比 |
10連ガチャ | 882 | 531 | 166% |
バトル初期化 | 848 | 276 | 307% |
バトル、スキル発動 | 391 | 214 | 182% |
クエストステージ一覧取得 | 587 | 251 | 233% |
法珠443個ギフト一括受け取り | 6,037 | 2,972 | 203% |
周回スキップx50 | 3,736 | 2,007 | 186% |
問題
PHP8は実行速度が早くとてもいいのですが、マジカミで使っている環境だと挙動が不安定になる現象を確認しました。
例えば、テンプレートエンジンsmartyの処理で以下の様な$mixedが配列以外なら、$_indexed_attrにフラグを追加するという処理があるのですが、たまに$mixedが配列でもif文の中に入ってしまう事がありました。phpのプロセスを再起動すると、再現したりしなかったり・・・
// shorthand ?
if (!is_array($mixed)) {
// option flag ?
if (in_array(trim($mixed, '\'"'), $this->option_flags)) {
$_indexed_attr[trim($mixed, '\'"')] = true;
// shorthand attribute ?
}
問題が起きたときの$mixedの内容は以下の通り
$mixed: ['item' => '"guide"']
どう見てもarrayですが。
この問題、is_array($mixed)の結果を一度変数に入れてからif文で判定したり、他の場所でis_array($mixed)を実行すると直ります。
↓こんなのとか
// shorthand ?
if (!is_array($mixed)) {
// option flag ?
var_dump(is_array($mixed)); //コレを追加すると直る
if (in_array(trim($mixed, '\'"'), $this->option_flags)) {
$_indexed_attr[trim($mixed, '\'"')] = true;
// shorthand attribute ?
}
もう、お手上げですw
コアダンプ吐かせてgdbで調べるのが早そうですかね。
最後に
意外と簡単にPHP7系からPHP8にアップデートすることができました。リリースするにはdeprecatedをちゃんと直したり、ライブラリーのアップデートが必要だと思いますが、思ってたより簡単でした。この導入コストでパフォーマンスが上がるなら入れない理由がない!と思っていたのですが、謎の挙動が発生して様子を見ることになりました。悲しい。