Article by: Richard C.
本ブログの内容
- 前提条件
- プロファイリングとは?
- SPX を使った PHP プロファイリング
- トレーシングとは?
- OpenTelemetry を使った PHP のトレーシング
- フロントエンドを含む分散トレーシング
- オンラインサービスを使ったプロファイリングとトレーシング
- クリーンアップ
- PHP のパフォーマンスを改善するヒント
- 次のステップ
PHP アプリは一見シンプルに見えますが、何かが遅くなり始めると状況は一変します。ページの読み込みに数秒余計にかかったり、原因がはっきりしないままサーバーコストが増加していたりしませんか?そんなときに役立つのがパフォーマンスモニタリングです。
このガイドでは、PHP アプリケーションのパフォーマンスを監視・改善する方法を解説します。プロファイリングやトレーシングを使ってコードのボトルネックを特定し、アプリを最適化する方法を学びましょう。Docker を使っている場合でも、すでにローカルに PHP をインストールしている場合でも、例に沿って進めることができます。
このガイドの内容は PHP 8 に対応しており、将来的な変更の程度によってはそれ以上のバージョンにも適用可能です。
前提条件
本ガイドに含まれるすべてのコード例は、Docker を使って任意のホスト OS 上で実行できます。Docker をお持ちでない場合は、こちらからダウンロードしてください。
すでに PHP がインストールされている場合、Docker は不要です。
次のセクションでは、プロファイリングとトレーシングを使って処理が遅いコード部分を特定する方法を紹介します。最後には、サイトのパフォーマンスを改善するためのベストプラクティスのチェックリストも掲載しています。
プロファイリングとは?
アプリケーションのプロファイリングとは、パフォーマンスデータを記録し、改善の余地があるコードを特定するために分析することです。パフォーマンス指標には、アプリがユーザーのために機能を実行するのにかかる時間、実行中に使用される CPU、メモリ、ディスク、ネットワーク帯域などが含まれます。これらすべての指標の値が低いほど、ユーザー体験が向上し、サーバーコストの削減にもつながります。
記録された結果を分析することで、意図よりも高い値の指標を特定し、その原因となっているコード行を見つけます。
一般的に、関数の実行にかかる時間(CPU 使用率と反比例することが多い)が、最も重要な改善指標です。メモリ、ディスク、ネットワークの使用については、比較的対処しやすいか、そもそも改善できない場合もあります。
扱うデータのサイズが既知であり、非効率なデータ構造を使っていたり、キャッシュなしでファイルに繰り返しアクセスしているようなケースを除けば、それらに対してできることは限られています。
SPX を使った PHP プロファイリング
人気のある PHP デバッグ拡張機能 Xdebug もプロファイリングをサポートしていますが、システムリソースの使用量が多いため、アプリのパフォーマンスを低下させ、現実的でない結果になる可能性があります。
その代わり、このガイドでは SPX(Simple Profiling eXtension)を使ったプロファイリングを紹介します。SPX はプロファイリング専用の軽量ツールで、Web レポートやフレームチャートを内蔵しています。SPX はまだ Windows 上では動作しませんが、PHP は通常 Linux サーバー上でデプロイされるため、問題にはなりません。
以下の手順に従って、Docker を使って SPX を実行します。
- コンピュータ上の任意のフォルダに dockerfile という名前のファイルを作成し、以下の内容を記述してください。標準的な PHP Docker イメージに SPX をインストールします。
- 次に、index.php ファイルを作成し、以下のコードを記述します。このコードは多重ループと入れ子関数を使って、処理時間の長い PHP ページロードをシミュレートします。
- php.ini ファイルを作成し、以下の内容を記述してください。
- これらのファイルと同じフォルダで、以下のコマンドをターミナルから実行します。最初のコマンドは Docker イメージをビルドします。2つ目のコマンドは、そのイメージに基づいて一時的なコンテナを実行し、上記の php.ini 設定を読み込んで SPX 拡張を有効にします。
- Chromium 系ブラウザまたは Firefox で、http://localhost:8000 と、別のタブで http://localhost:8000/?SPX_KEY=dev&SPX_UI_URI=/ にアクセスしてください。2つ目のタブで SPX の Web UI が表示されます。
- 最初のタブを更新し、その後に2つ目のタブも更新します。画面下部にレポートのリンクが表示され、クリックすると詳細レポートを開けます。新しいレポートを確認したいときは、両方のタブを更新してください。
以下は Web UI 設定ページの例で、利用可能なレポートが下部に行として並んでいます。
SPX の設定画面
以下は SPX レポートです。
SPX レポート
このレポートでは、index.php にある go() と innerGo() の 2つの関数が表示された flame chart を確認できます。フレームチャートは、関数呼び出しのネストされた階層を色付きのバーで視覚化し、時間に沿った実行スタックトレースを示します。これにより、アプリがどの関数に多くの時間を費やしているかや、関数の呼び出し順序(実行フロー)を確認できます。
フレームチャートは、以下のような構成で関数を表示します。
- 横軸:経過時間を左から右へ示し、関数の呼び出し順と実行時間を時系列で表します。
- 縦軸:スタックの深さ、つまり関数呼び出しの階層を示します。各関数呼び出しは矩形のブロックで表され、ブロックの高さがその時点でのスタックの深さを表します。
- 色:関数やモジュールの種類に応じて色分けされており、コードベースの特定部分と視覚的に関連づけてパターンを見つけやすくなっています。
チャートには、go() 関数に費やされた合計時間(「Wall time」と呼ばれます)と、innerGo() 関数にかかる短いスパイク時間が表示されます。
左下の表では、各関数にかかる正確な時間の割合が確認できます。Wall time(ウォールタイム)は、リアルタイムまたは経過時間とも呼ばれ、プロセスや操作の開始から終了までに実際にかかった時間を示します(“wall time” という名前の由来は、壁の時計で計測される時間という意味です)。これは、I/O 操作の待機時間やネットワーク遅延など、外部要因も含めた処理完了までにユーザーが待つ全体の時間を表します。このシンプルな例では I/O が発生しないため、Wall time は CPU time と等しくなります。
左上のコンボボックスを使えば、CPU time や RAM 使用量など、ページリクエストに関する他の指標も表示できます。Wall time は CPU time と異なり、CPU がコードを実行していた時間だけを測る CPU time とは区別されます。プロファイリングにおいては、Wall time と CPU time の両方を考慮することが重要です。Wall time はエンドツーエンドの全体的なパフォーマンスを示し、CPU time はコード内部の計算負荷の高いボトルネックを測定します。
SPX は、このデモ用の簡単な例でも、複雑な Laravel アプリケーションでも同様に有効に機能します。どちらの場合も、ユーザーからのページリクエストはフレームチャートとして可視化されます。SPX によって表示されるチャートや指標を使えば、アプリケーション内のボトルネックや、CPU や RAM 使用量の急増といった他の問題を特定できます。実際のアプリケーションで SPX を使用するには、上記の Dockerfile のコードを使ってサーバーに SPX をインストールし、上記の php.ini の設定を INI ファイルに追加してください。
ただし、SPX を使用すると、ハッカーがサイトを攻撃するための侵入口(アタックサーフェス)が広がる点には注意が必要です。本番サーバーではなく、QA(品質保証)用サーバーで実行するのが望ましいです。強力なパスワードを設定し、管理者の IP アドレスだけをホワイトリストに登録するなど、特にセキュリティ対策を徹底してください。詳細については、SPX の README.md ファイルをご確認ください。
トレーシングとは?
プロファイリングでは、アプリの内部スタックトレースや各関数の実行時間がわかります。しかし、ユーザーのリクエストがすべてのサービスを通って進む経路全体を把握するには、分散トレーシングを使う必要があります。たとえば、あなたのサイトのユーザーが、注文した商品のステータスを知りたいとします。もしサイトがモノリシックな構造ではなく、認証用と注文処理用に分かれた2つのサービスで構成されていた場合、ユーザーのリクエストは次のように処理されます。
- サイトは、注文番号に対応する注文情報を取得するためのユーザーの POST リクエストを受け取る。
- ウェブサーバーは、ユーザーのクッキーが有効かどうかを確認するため、認証サービスに内部 HTTP リクエストを送る。
- その後、注文サービスに注文情報をリクエストし、結果をユーザーに返す。
トレーシングの用語では、複数サービスをまたいだリクエスト全体の流れを「トレース(trace)」、各サービス内で行われる処理単位を「スパン(span)」と呼びます。親サービス(たとえば Web サイト)が子サービス(たとえば注文サービス)を呼び出す際には、ランダムなスパン ID を一緒に渡します。トレーシングツールは、すべてのサービスをまたいだ実行の流れを監視し、スパン ID をつなぎ合わせて、1つのトレース(リクエスト全体の記録)としてまとめます。 これはすべてのユーザーリクエスト、またはその一部を対象にして行われます。
OpenTelemetry を使った PHP のトレーシング
トレースやパフォーマンスメトリクスを記録するための標準プロトコルは OpenTelemetry です。
OpenTelemetry に基づいて構築されたツール群には、PHP 向けのトレーシングツールも含まれています。OpenTelemetry を使った自動トレースの記録方法を簡単に示すために、ここでは Slim というマイクロフレームワークを使います。さらにシンプルなフレームワークとして Flight もありますが、OpenTelemetry 用の自動化ライブラリが用意されていません。Laravel、Symfony、WordPress 向けのパッケージも用意されています。
単一サービスへの自動トレーシング
まずは、OpenTelemetry を使って単一の Web サーバーに自動トレーシングを追加するところから始めましょう。
これは前のセクションで扱ったプロファイリングと似た流れです。OpenTelemetry ではこれを「ゼロコードインストゥルメンテーション」と呼び、コード上でトレースの開始や終了を明示的に記述する必要がありません。
残念ながら、本記事執筆時点では PHP 向け OpenTelemetry の公式ドキュメントには不備があります。依存関係が不足しており、提供されているサンプルも動作しません。そこで本ガイドでは、必要な PHP パッケージと Docker 上で確実に動作する具体的なコードを提供します。
まず、作業用の空のディレクトリを作成し、そのディレクトリでターミナルを開いてください。
dockerfile という名前のファイルを作成し、以下の内容を記述します。この内容は、PHP 8.3 をベースにしたイメージを作成し、Composer をインストールし、OpenTelemetry 関連の拡張と Slim フレームワークを追加するものです。
Docker イメージをビルドします。C 言語で gRPC 拡張をコンパイルする必要があるため(pecl install grpc の行)、ビルドには最大で1時間ほどかかる場合があります。
上記のコマンドが実行されている間に、index.php というファイルを作成し、以下の内容を記述してください。このコードは、最小構成の Web サーバーで、$app->get(‘/’) というルートにアクセスすると callNetwork 関数が実行され、外部ページの取得に時間がかかる処理を経て、ユーザーに Hello world を返します。open-telemetry/opentelemetry-auto-slim 拡張によりトレーシングが自動で処理されるため、OpenTelemetry に関するコードは一切含まれていません。
SPX のプロファイリングとは異なり、OpenTelemetry はトレースデータを出力するだけで、それ自体に表示機能はありません。トレースを収集・可視化するための最もシンプルなツールが Jaeger です。Jaeger は別の Docker コンテナで実行します。
まず、PHP コンテナと Jaeger コンテナが通信できるように、共通の Docker ネットワークを作成します。(PHP や Jaeger はローカルホスト上でポートを公開していますが、コンテナ内では localhost は自身を指すため、別のコンテナを参照するには共有ネットワーク上で名前指定する必要があります。)
次に、新しいターミナルで以下のコードを実行して、ネットワークを作成し、Jaeger を起動してください。
http://localhost:16686 にアクセスして、Jaeger のダッシュボードが表示されることを確認してください。
Jaeger
ページを更新すると、サービスの数が 0 から 1 に増えるのが確認できます。これは、Jaeger が自身を監視しているためです。
上部のドロップダウンから Jaeger を選択し、下部の Find Traces をクリックします。任意のトレースをクリックすると、詳細情報を展開して確認できます。
PHP の Docker イメージのビルドが完了したら、同じターミナルで以下のコマンドを実行します。これにより、必要な PHP モジュールが vendor サブディレクトリにインストールされ、その後 Slim が起動されます。環境変数によって OpenTelemetry が有効になり、トレースが http://jbox:4317 に送信されます。
http://localhost:8000 にアクセスすると、「Hello world!」と表示されるはずです。
Jaeger の画面に戻ると、サービス一覧に app が表示されるはずです(これは OTEL_SERVICE_NAME=app によって指定されたものです)。
エラーを含む Jaeger
使用しているブラウザによっては、2 つのトレースが表示されるはずです。
1 つはネットワーク経由の外部呼び出しを行ったため 1 秒以上かかったもので、もう 1 つは数ミリ秒で終わるエラー付きのトレースです。エラーのあるトレースが表示された場合は、それを展開して詳細を確認してください。このエラーは、おそらくブラウザが http://localhost:8000/favicon.ico をリクエストしたために発生したもので、そのファイルは存在しません。この例は、普段サイトを閲覧していては気づかないようなエラーを、トレーシングによって検出できることを示しています。
Jaeger にトレースが表示されない場合は、gRPC を使う代わりに、トレースをターミナルに出力するようにしてみてください。
Web サイト用の Docker 実行コマンドにおいて、OTEL_TRACES_EXPORTER=otlp の代わりに OTEL_TRACES_EXPORTER=console を指定します。ターミナルにはトレースが表示されるのに Jaeger に表示されない場合は、ネットワークの問題が原因です。どこにもトレースが表示されない場合は、OpenTelemetry が Slim を正しく監視できていない可能性があります。すべての PHP パッケージが正しくインストールされていること、および環境変数が正しく設定されていることを確認してください。
単一サービスに対する明示的トレーシング
Jaeger 上でエラーのないトレースを確認すると、OpenTelemetry が計測しているのは Slim の外側の GET メソッドだけであり、実行時間の大部分を占める callNetwork 関数の中までは追跡されていないことがわかります。
Jaeger タイムライン
より細かく調査したい場合は、すべての関数を対象とするプロファイリングと組み合わせるか、特定の関数に対して OpenTelemetry のインストゥルメンテーションコードを手動で追加する方法があります。
ここでは、OpenTelemetry を手動で使う方法を見ていきましょう。
index.php の内容を以下のコードに置き換えてください。
変更後のコードでは、callNetwork() の前後に新しいスパンを開始・終了する 2 行が追加されています。これにより、開発者が明示的にスパンを作成できるようになります。
http://localhost:8000 を更新し、Jaeger に戻って新しいトレースを確認してください。最新のトレースを確認すると、ネットワーク呼び出しのために明示的に記録されたスパン があり、その処理がリクエスト全体の 90%以上の時間を占めていることがわかります。
Jaeger 明示的な スパン
PHP における分散トレーシング
ここまで見てきたトレーシングの例は、プロファイリングと大きな違いがあるわけではありません。分散トレーシングを構成することで、サービス間のリクエストの流れをより深く理解できるようになります。この最後のトレーシング例では、2 つの別々の PHP サービスを動かし、1 つのリクエストが両方のサービスを通過してユーザーに戻る流れを Jaeger で監視する方法を示します。
トレース ID を使うことで、ユーザーのリクエストが複数のサービスをどのように流れるかを追跡できます。あるサービスがユーザーのリクエストに応答する際、OpenTelemetry はランダムなトレース ID を付与します。この親サービスが子サービスを呼び出すとき、トレース ID はリクエストヘッダーに含まれて渡されます。これを「伝播(propagation)」と呼びます。
Slim が HTTP リクエストを受け取ると、OpenTelemetry はそのリクエストのトレースを自動的に Jaeger に送信します。2 つのサービスが関与している場合、Jaeger には 2 つのトレースが送信されます。しかし、子サービスのリクエストに親サービスのトレース ID が含まれているため、Jaeger は自動的に子トレースを親トレースにネストし、1 つのトレースとして表示します。
以下は、サービス間で送受信されるメッセージの流れを示す図です。上から下へ読むことで、メッセージの送信順序がわかります。
メッセージフロー図
それでは、この例を実際にコードで実装してみましょう。
- parent.php というファイルを作成し、以下のコードを記述します。このコードにはトレース ID を直接扱うための OpenTelemetry\API パッケージが含まれています。また、別のサービスに HTTP リクエストを送る処理を簡潔にするために、GuzzleHttp パッケージも使用しています。親サービスの GET メソッドでは、Guzzle を使って childbox に対するリクエストを作成し、TraceContextPropagator::getInstance で現在のトレース ID を取得し、それを $childRequest->withAddedHeader($name, $value) によってリクエストヘッダーに追加します。その後リクエストを送信し、結果をユーザーに返します。最後の /favicon.ico エンドポイントは、Jaeger にエラーが表示されないようにするための簡単な対策です(ブラウザがファビコンをリクエストするのを処理します)。
- 次に、child.php というファイルを作成し、以下の内容を記述します。このコードはもともとの例に近く、単にテキストを返すだけのシンプルな処理です。親リクエストからトレース ID を明示的に取得する必要はありません。OpenTelemetry が自動的に処理してくれます。
- 以前 index.php を実行していたターミナルで、Ctrl + C を押して停止し、以下のコマンドで親サービスを起動します。コマンドの最後で、デフォルトの index.php ではなく実行対象のファイルを明示的に指定している点に注意してください。
- 新しいターミナルを開き、以下のコマンドで子サービスを起動します。この Docker コンテナには childbox という名前を付けています。これは、親サービスがネットワーク上でこのコンテナを参照できるようにするためで、Jaeger を jbox と名付けたときと同様の理由です。
- http://localhost:8000 のページを更新し、Jaeger に戻ります。
- Jaeger 上で新しいリクエストを確認すると、子サービスの呼び出しが親サービスの中にネストされて表示されているのがわかります。
2つのサービスを横断する Jaeger のトレース
依存関係のある複数のマイクロサービス群を運用している場合でも、OpenTelemetry と Jaeger を使えば、1つのリクエストがすべてのサービスをどのように通過したかを、複数のスパンを含む単一のトレースとして正確に可視化できます。
フロントエンドを含む分散トレーシング
フロントエンドアプリ(ネイティブモバイルアプリまたは Web アプリ)を分散トレースに含める手順は、サーバー側の実装とよく似ています
- iOS、Android、または JavaScript 用の OpenTelemetry ライブラリをアプリに追加します。
- フロントエンド側のコードで、トレース情報を Jaeger に送信する処理を追加します。
- サーバーへのリクエストにトレース ID を含めるようにします。
親トレース ID が適切に伝播されていれば、Jaeger はユーザーからサーバー、さらにはデータベース呼び出しまで、リクエストの全経路を 1 つのトレースとして統合して表示してくれます。
オンラインサービスを使ったプロファイリングとトレーシング
ここまでは無料ツールを使用してきましたが、アプリケーションを常時監視し、エラーの通知を受け取り、パフォーマンスのダッシュボードを持つためには、より複雑な仕組みが必要です。
Prometheus や Elastic のような無料ツールを自前のサーバーに構築することも可能ですが、設定が複雑で、ユーザー数の増加に応じて自分でスケーリングと保守を行う必要があります。さらに、モニタリング用のサーバーがオフラインになると、問題が発生しても通知されません。
より現実的な方法は、有料の監視サービス、もしくはその無料スタータープランを利用することです。これにより、シンプルなセットアップで常時利用可能なモニタリングが実現します。
複数の無料ツール(プロファイリング、トレーシング、エラーモニタリング)を組み合わせる代わりに、1 つの有料ツールにまとめることで、管理しやすく一元化されたソリューションを手に入れることができます。
このセクションでは、Sentry の無料プランを使ってアプリケーションのプロファイリングとトレーシングを行う方法を紹介します。以下の手順に従って、Sentry アカウントを作成し、アプリと接続して、Web インターフェース上でアプリの監視を行いましょう。
まず、これまでのセクションで起動していたすべての Docker コンテナをターミナルで停止してください。既存のディレクトリを再利用してファイルを上書きしても、新たにディレクトリを作成して作業してもかまいません。
https://sentry.io/signup にアクセスしてアカウントを作成します。オンボーディングはスキップしてください。
Create project をクリックします。
- PHP を選択
- プロジェクト名を入力
- Create Project をクリック
次のページではセットアップ手順が表示されますが、その中の DSN URL をメモしてください。これは秘密情報であり、GitHub にコミットしないよう注意してください。
次に、作業ディレクトリに dockerfile というファイルを作成し、以下の内容を記述してください。(これまでのガイドで使ったファイルは不要になるため、上書きしても問題ありません。)
sbox という名前で Docker イメージをビルドします。
Sentry の利用に必要な Composer 依存パッケージをインストールします。
以下のコードを使って parent.php ファイルを作成します。Sentry の初期化部分にある https://YOUR_DSN は、先ほど取得した DSN に置き換えてください。
\Sentry\init([ の行は、PHP アプリに Sentry を組み込むための初期化処理です。Sentry の初期化オプションについての詳細は公式ドキュメントに記載されています。ここでは Sentry のキー(DSN)を設定し、トレースとプロファイルを 100% 収集するように指定します。また、Sentry がサーバーに接続できなかった場合に備えて、エラーをターミナルに表示するようにしています。
前の例と同様に、Slim の GET メソッド内で Guzzle を使って子サービスを呼び出します。子サービスを呼び出す前に、新たな Sentry トランザクションを作成し、スパンを追加します。そして、トレースを伝播させるために必要なヘッダー情報を Guzzle のリクエストに挿入します。Laravel や Symfony を使っていない場合、このような明示的なトレーシング処理が必要になります。Laravel や Symfony のようなフレームワークでは、Sentry が自動トレーシングを提供しています。
次に、以下のコードで child.php を作成します。初期化部分の https://YOUR_DSN は、取得済みの DSN に置き換えてください。このコードは parent.php と似ていますが、親サービスからのトレース情報を GET リクエストヘッダーから取り出し、\Sentry\continueTrace により新しいトランザクションを開始している点が異なります。
子サービスを起動します。
親サービスを起動します。
http://localhost:8000 にアクセスします。
Sentry の Web インターフェースで Traces ページを開きます。トレースが表示されるまで数分かかる場合がありますが、下図のように表示されるはずです。Sentry は、OpenTelemetry と同様に、親トレースと子トレースを同じ trace ID で関連付けて表示しているのが確認できます。
Sentry トレース
Sentry トレース
Sentry はプロファイリング情報を自動で収集します。アプリケーションのパフォーマンスを確認するには、Sentry の Web インターフェースで Profiles を開いてください。任意のリクエストに対するフレームグラフやコールツリーを確認できます。
Sentry profiles
Sentry profiles
クリーンアップ
このガイドでは、複数の Docker コンポーネントがローカル環境に作成されました。それらを削除したい場合は、以下のコマンドを実行してください。
PHP のパフォーマンスを改善するヒント
以下に紹介する PHP の最適化のヒントのうち、簡単に実行できるものはすぐに取り入れ、Redis のような複雑な手法は必要に応じて導入してください。
言語と並列処理に関するヒント
- PHP や Web サーバーは常に最新バージョンを使用してください。新しいバージョンには多くの最適化が含まれており、アプリケーションの高速化に役立ちます。
- 処理が遅く長時間かかる関数については、非同期処理に移行することでメインの Web サーバーをブロックしないようにできます。たとえば、アップロードされた動画の処理などをバックグラウンドキューに移動し、CPU を占有しないようにしましょう。RabbitMQ や Beanstalkd のようなキュー管理アプリの利用を検討してください。
ネットワークとキャッシュに関するヒント
- 別の Web サーバーの利用を検討してください。Apache を使用している場合は、Nginx や Caddy に変更してみましょう。遅さの原因はコードではなく、Web サーバー側のリクエスト処理がボトルネックになっている可能性があります。
- Web サーバーで圧縮を有効にし、ネットワーク経由で転送されるデータ量を削減しましょう。
- 画像、音声、CSS、JavaScript などの静的アセットは、アプリケーションサーバーではなく CDN(コンテンツ配信ネットワーク)から配信するようにしてください。これによりサーバーの負荷が軽減され、ユーザーの読み込み速度も向上し、静的 CDN は動的ホスティングよりも安価であるため、コスト削減にもつながります。
- 頻繁に使われるデータの取得には、データベースやネットワーク呼び出しの代わりに、Memcached や Redis を使用してください。メモリ内キャッシュアプリは、ディスクやリモートサーバーにアクセスするよりもはるかに高速にデータを提供します。頻繁に変わらないデータ、少し古くても問題ないデータ、ユーザーに表示する前に計算が必要なデータなどは、キャッシュすることを検討しましょう。
- 本番サーバーでは、OPCache を有効にしてください。OPCache は、PHP スクリプトのコンパイル済みコード(オペコード)をキャッシュし、毎回再生成する必要がなくなります。
- オートローダーを活用しましょう。クラスのオートロードを使えば、必要なときにのみ他のスクリプトを読み込むことができます。これにより、使わないコードを読み込むことで無駄な時間を費やすことを防げます。また、どこでもクラスをインスタンス化できるため、include などを明示的に書く必要がなくなり、プログラムが簡潔になります。
PHP 言語に関するヒント
- グローバル変数の使用は最小限に抑えましょう。 グローバル変数へのアクセスは、ローカル変数よりも遅くなります。ローカル変数は関数の終了時にガーベジコレクターによって解放されるため、メモリ(RAM)の使用量削減にもつながります。
- 文字列にはダブルクオートではなくシングルクオートを使いましょう。シングルクオート文字列は変数展開の対象にならず、PHP はそのままリテラルとして扱うため、処理が速くなります。
- 非常に大きな配列では、array_key_exists() の代わりに isset() を使いましょう。isset() は通常の関数ではなく言語機能として組み込まれており、呼び出しが速くなります。ただし、その差はわずかなので、大規模な配列を扱うときにのみ効果が現れます。
- 大きなファイルを扱う際は、file_get_contents() の代わりに fopen()、fread()、fclose() を使いましょう。file_get_contents() はファイル全体を一度に読み込むため、大きなファイルでは処理が遅くなり、RAM を大量に消費します。
- 文字列の処理には、正規表現ではなくネイティブの文字列関数を使いましょう。strpos()、substr()、strlen()、str_replace() などの関数で問題を解決できない場合のみ、正規表現を使うようにしてください。正規表現は処理が非常に遅くなります。
- 自作コードを書くより、ネイティブの PHP 関数を使いましょう。PHP の組み込み関数は C 言語で実装されており、マージ、ソート、結合などの処理において、自分で書いたコードよりも高速に動作します。
- マジックメソッド(オブジェクトのライフサイクルメソッド)は使用しないでください。これらは PHP でアンダースコアから始まるメソッド(例:__destruct())のことで、オブジェクトを編集する明示的なメソッドを自分で書くよりも遅くなります。たとえば、クリーンアップコードを実行するためにガーベジコレクタを強制的に動かす目的で __destruct() を使うのではなく、自分で close() メソッドを作成し、オブジェクトが不要になったタイミングでそれを呼び出すようにしてください。
- 長時間実行される関数内で不要になった大きな変数には unset() を使いましょう。これにより、次回ガーベジコレクタが実行された際に削除対象としてマークされ、メモリと時間の節約につながる可能性があります。
- 動的な関数を作るために eval() を使うのは、他に手段がない場合のみにしてください。eval() は通常の PHP コードよりもはるかに遅く、コンパイルやキャッシュの対象にもなりません。
一般的なヒント
- データベースクエリを最適化しましょう。SQL コードはできるだけ短く書き、インデックスを使用し、不要なクエリの実行は避けてください。
- 本番環境では、不要なロギング、パフォーマンスプロファイリング、データベースクエリのロギング、メモリ使用量の追跡など、不要なデバッグコードは無効にしましょう。
- CPU は速く動作することはできず、より少ないタスクをより短時間で実行することしかできません。そのため、コードの高速化を検討する際には常に、「どうすればより単純化できるか?繰り返しを減らしても同じ結果を得られるか?」と問いかけてください。
次のステップ
PHP におけるエラー検出と通知について詳しく知りたい方は、PHP のロギングとデバッグガイドをご覧ください。
Sentry がデバッグにどのように役立つかを知るには、トレーシングとプロファイリングのガイドをご覧ください。
Original Page: How to Improve Performance in PHP
IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。