Laravel.shibuya がなんと Final !! お世話になったので、少しでも盛り上げたく、 LT することになりました。このブログポストでは、スライドだけでは分かりづらい内容を少し文章で補足しています。
https://laravel-shibuya.connpass.com/event/250113/
スライド
PHP の設定による低速化
長いこと PHP を扱ってきたプログラマーなら当たり前のことが、最近 PHP を触り始めた人にはわからなかったりします。地味ですが、PHP ウェブアプリケーションの性能を劣化させる方法を4つほど紹介しました。
1. preload 無効化
そもそも、preload を使ってない現場が多いと思いますが、CPU バウンドなウェブアプリケーションであればパフォーマンスに大きな効果を発揮します。そもそも、preload って何よ?っていう方は、拙スライドを参考にどうぞ。
ソースコードから理解するPreloadとJITの話/preload_and_jit - Speaker Deck
2. opcache.jit
PHP 8.0 以降は、JIT 最適化がデフォルトで有効です。こちらも CPU バウンドな処理に効果を発揮します。しつこいようですが、拙スライドで仕組みを解説してますので、わからない方はそちらを参考にしてください。
3. opcache 無効化
自分でコンテナ環境を作る方は、よく分かっていると思いますが、PHP の公式 Docker イメージは、OPCache がそもそもインストールされていません。ご自身で pecl install opcache する必要があります。
これを無効化することで、50%弱の速度劣化を引き起こすことが出来ます。とても効き目がありますので、もし不安のある方は自分のウェブアプリケーションが OPCache が有効な状態で動いていることを確認しましょう。確認方法は簡単です。
1  | $ php -v  | 
OPCacheがインストールされていれば、with Zend OPCache という記述が見られます。また、有効化されていれば opcache.enable => On と表示されます。
4. Xdebug 有効化
Xdebug は PHP ウェブアプリケーション開発になくてはならないデバッガーですが、余計なオーバーヘッドが発生するためパフォーマンスに影響があります。本番環境で Xdebug を有効にする意味はありませんので、心配な方はチェックしてみましょう。確認方法は、OPCache の時と同じです。
1  | PHP 8.1.7RC1 (cli) (built: Jun 1 2022 08:43:27) (NTS)  | 
with Xdebug と表示されている場合は、Xdebug が少なくともインストールされています。本番動作時は無効化されている場合もありますので、さらに php -i で設定を確認してみてください。まあ、誤って有効化してしまう可能性があるので、ini ファイルで Xdebug のモジュールを読み込まないようにするのが安全だとは思います。
Laravel の書き方による低速化
1. eager loading 廃止
今回、もっとも効果が高かった低速化施策はコレでした。With メソッドを使うのをやめて、1レコードずつ関連するテーブルの情報を都度検索することで、著しく低速化させることが可能です。いわゆる N + 1 というやつです。
1  | posts = \App\Models\Post::take(20);  | 
2. limit 句廃止
「そんなことするやついない」と思われるかもしれませんが、意外と見かけるのがコレです。わかりにくいですが、書き換えた方の処理だとデータベースから posts テーブルの情報を全件取得したあとに Collection クラスの take メソッドを使って 20 件に絞っています。ネットワーク上を posts テーブルのデータが全件旅するのを想像すると、ゾッとしますね。
1  | //$posts = \App\Models\Post::take(20)->get();  | 
3. attribute casting の乱用
Laravel の Eloquent Model には Attirbute Casting という仕組みがあります。特にデフォルトで用意されている updated_at, created_at は、プロパティとして参照しただけで、内部でキャスト処理が実行されて、Carbon インスタンスが生成されます。この生成処理が馬鹿に出来ないほど高コストです。
tinker を使って、手元で計測してみましたが、5000 件程度の Collection に対して、created_at, updated_at のプロパティアクセス2回行うと、200 msec 程度の時間が生成だけで使われてしまいます。こんな行をループ内に何個も書いてしまったら……。ちょっと怖いですね。
1  | foreach($posts as $post){  | 
まとめ
現場で普通に書かれていそうなコードでも、50 msec だった応答速度を、簡単に 1.5 sec くらいまで下げることが出来ました。実際のソースコードはもっと複雑でしょうから、さらに性能劣化が起きてしまっても不思議ではありません。
でも、普段からちょっと気をつけるだけで、性能劣化を起こしにくいソースコードにすることは可能です。少なくとも、今回挙げたような例については、確認してみることをおすすめします。結構、簡単ですよ。