こんにちは、あるいはこんばんは。すぱ..すぱらしいサーバサイドのエンジニアの(@taclose)です☆
なんと嬉しい事に PHPerKaigi 2023 での登壇が決まりました☆この記事出る頃には登壇終わってるけど!(汗)
題材は「パフォーマンスを改善せよ!大規模システム改修の仕事の進め方」
https://fortee.jp/phperkaigi-2023/proposal/4a67cc68-83f0-492d-86ca-54304fc256c8
本セッションではパフォーマンス改善の具体的な手法まで深掘りせずに、広く浅く触れていこうかなと考えていますので、是非マネージャーなんかもご視聴頂ければと思います!
という事で今回このブログでは逆に技術に深掘りした内容を話しちゃおうと思います!
今回はPHPのパフォーマンスチューニングの1つにあるOPcacheの中でもpreloadという機能に着目して説明していきたいと思います!
結論からいうと、
OPcacheのpreloadを使う事でphp処理が相当速くなる可能性があります!!
是非、皆さまの作成しているサイトでも採用を検討してくださいませっ!
OPcacheとは?
私たちが普段書いているPHP言語がコンパイルされたコードをOPcode(オペコード)と呼ばれており、それをメモリー上に保持(キャッシュ)する事で、PHPの処理を高速化しよう!という機能。
これまでもそういう取り組みはあったんですが、PHP5.5からはOPCacheが標準機能として採用され今に至っています。
preloadとはどんな機能なのか?
preloadはOPcacheの更に一部の機能です。先ほどはOPcodeをメモリ上にキャッシュする所までやりましたが、
「じゃもぉこれ事前にloadしておいたらautoloadとか不要で更に早いんじゃないの?」という機能になります。
事前にload = pre-load うん、そのままですね!
絵で描くとこんな感じですね!(PHPerKaigi2023の登壇資料より抜粋)
preloadの設定をやってみよう!
preloadが何か?がわかったので、具体的な設定の例を以下に記載しておきます。
[opcache] zend_extension=/path/to/opcache/opcache.so opcache.enable = 1 opcache.preload=/path/to/preload/preload.php opcache.preload_user=root opcache.jit_buffer_size = 256M opcache.memory_consumption=256 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=100000 opcache.revalidate_freq=0 opcache.enable_cli=1 opcache.validate_timestamps=0
opcacheのオプションの細かい意味は公式サイトにまとめられていますので、こちらを参考にしてください。
preload.phpの中身
今回私の方では以下のようにしています。
<?php function _preload($preload, string $pattern = "/\.php$/", array $ignore = []) { if (is_array($preload)) { foreach ($preload as $path) { _preload($path, $pattern, $ignore); } } else if (is_string($preload)) { $path = $preload; if (!in_array($path, $ignore)) { if (is_dir($path)) { if ($dh = opendir($path)) { while (($file = readdir($dh)) !== false) { if ($file !== "." && $file !== "..") { _preload($path . "/" . $file, $pattern, $ignore); } } closedir($dh); } } else if (is_file($path) && preg_match($pattern, $path)) { if (!opcache_compile_file($path)) { trigger_error("Preloading Failed", E_USER_ERROR); } } } else { echo "IGNORE: $path\n"; } } } _preload(["/var/www/application"], "/\.php$/", ["/var/local/application/ignore/hoge.php]);
_preloadメソッドは指定フォルダにある、指定拡張子のファイルを再帰的にpreload対象にするメソッドとして定義しています。 3つ目の引数でその中で除外したいファイルを指定できるようにしています。 正直ベストアンサーだとは言えませんが、とりあえず効果を見たいのであれば 普段spl_autoload_register()を使って読み込んでいるファイルを網羅的に指定すればOKです!
どのくらい動的にインクルードされているのか?見てみよう。
さて、設定が終わった人はきっと不安な事でしょう。
「動いてるけど、効果出てるの...?」
実際、1ページのロード時間でいうと体感できるかは難しいかと思います。
よって設定が有効が働いているかの確認は以下が宜しいかと思います。
- preloadの設定解除して
count(get_included_files())
を出力 - apacheを再起動
- preloadの設定して
count(get_included_files())
を出力
この数の差がpreloadで事前ロードが成功した数になります。 ※上記は動的にロードされたファイル数をカウントしています。なぜプレロード(事前ロード)されなかったのか?については長くなるので次のブログに書きますね!
preloadの検証結果
試しに、私の担当する商材に対して50画面x4回=200回のアクセスを計測してみたところ、
約2.8%の改善効果がありました。
この数字はあくまで参考値だと思って下さい。というのは、今回早くなったのはファイルを事前ロードしておく事による効果です。 つまり、事前ロードにかかってる時間が占める割合が元々大きければ大きいほど改善するという事になります。
上記は2023年3月24日のPHPerKaigi2023の登壇資料の一部です。私の担当する商材をプロファイリングした結果になるのですが、遅い原因がDBであればそこを改善しない事には改善しません。 ですが、実はこの登壇資料中では語っていませんが、上から2番目にあるSelf:5.50% となっている場所はspl_autoload_register()の処理にかかった時間の割合を指しています。 つまりこの場合は最大5.5%の改善が見込めるという事ですね!
さいごに
皆さまも、是非ご担当されているサービスが遅い原因が何かを計測した後には、色んな改善方法があるんだなぁという一環でお試し頂ければと思います!
参考文献
Symfony DOCS - Performance Amazon Linux 2のPHP 7.4環境に、OPcacheを導入する OPcache のインストール手順 PHP7.4のpreloadいれたらLaravelは早くなるのだろうかと思って検証した @mpywさんの個人的なTweet ソースコードから理解するPreloadとJITの話 PHP: OPcache - Manual