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 くらいまで下げることが出来ました。実際のソースコードはもっと複雑でしょうから、さらに性能劣化が起きてしまっても不思議ではありません。
でも、普段からちょっと気をつけるだけで、性能劣化を起こしにくいソースコードにすることは可能です。少なくとも、今回挙げたような例については、確認してみることをおすすめします。結構、簡単ですよ。