ホームページ >バックエンド開発 >PHPチュートリアル >[转]PHP の最適化に関する HOWTO
来源:http://phplens.com/lens/php-book/optimizing-debugging-php.php
最終改訂日 2005 年 2 月 28 日。変更内容を確認したい場合は、この日付を検索してください。この記事。
この記事が気に入ったら、私のブログ、PHP Everywhere にアクセスして関連記事をご覧ください。
PHP は非常に高速なプログラミング言語ですが、PHP の最適化にはコードの実行速度だけではありません。
この章では、PHP の最適化にコードとは関係のない多くの要素が関係する理由、および PHP のチューニングにサーバー上の他のすべてのサブシステムとの関係で PHP がどのように動作するかを理解し、これらのサブシステムによって引き起こされるボトルネックを特定して修正する必要がある理由を説明します。彼ら。また、PHP スクリプトをさらに高速に実行できるように調整および最適化する方法についても説明します。
高いパフォーマンスの達成
優れたパフォーマンスについて話すとき、PHP スクリプトの実行速度について話しているのではありません。パフォーマンスは、スケーラビリティと速度の間の一連のトレードオフです。使用するリソースが少なくなるように調整されたスクリプトは、キャッシュを実行するスクリプトよりも遅くなる可能性がありますが、同じスクリプトのより多くのコピーを Web サーバー上で一度に実行できます。
以下の例では、A.php は速く走ることができる短距離ランナーであり、B.php はほぼ同じ速度で永遠にジョギングできるマラソンランナーです。負荷が軽い場合は、A.php の方が大幅に高速ですが、Web トラフィックが増加すると、B.php のパフォーマンスが少し低下するだけで、A.php は力不足になるだけです。
より現実的な例を見てみましょう。問題をさらに明確にするため。 250K ファイルを読み取り、ファイルの HTML 概要を生成する PHP スクリプトを作成する必要があるとします。同じことを行う 2 つのスクリプトを作成します。hare.php はファイル全体を一度にメモリに読み込み、1 回のパスで処理します。もう 1 つは、一度に 1 行ずつファイルを読み込み、最長行以上は保持しない tortoise.php です。記憶の中で。 Tortoise.php は複数の読み取りが発行されるため遅くなり、より多くのシステムコールが必要になります。
Hare.php は 0.04 秒の CPU と 10 Mb RAM を必要とし、tortoise.php は 0.06 秒の CPU と 5 Mb RAM を必要とします。サーバーには 100 MB の実際の空き RAM があり、CPU は 99% アイドル状態です。話を単純化するために、メモリの断片化が発生しないと仮定します。
10 個のスクリプトを同時に実行すると、hare.php がメモリ不足になります (10 x 10 = 100)。この時点では、tortoise.php にはまだ 50 MB の空きメモリが残っています。 11 番目の同時スクリプトを実行すると、hare.php が仮想メモリの使用を開始し、速度が元の半分程度に低下します。 hare.php の各呼び出しには 0.08 秒の CPU 時間がかかるようになりました。その間、tortoise.php は引き続き通常の 0.06 秒の CPU 時間で実行されます。
以下の表では、さまざまな負荷に対する高速な php スクリプトが太字で示されています:
接続 | 1 つの HTTP リクエストを満たすのに必要な CPU 秒数 | 10 の HTTP リクエストを満たすのに必要な CPU 秒数
| 11 の HTTP リクエストを満たすのに必要な CPU 秒数
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hare.php
| 0.04
| 0.40
| 0.88 (RAM が不足しています) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0.06
| 0.60
| 0.66
|
Apache は Unix と Windows の両方で使用できます。世界で最も人気のある Web サーバーです。 Apache 1.3 は、Web サービスに事前フォーク モデルを使用します。 Apache が起動すると、HTTP リクエストを処理する複数の子プロセスが作成されます。最初の親プロセスは守護天使のように機能し、すべての子プロセスが適切に動作していることを確認し、すべてを調整します。より多くの HTTP リクエストが受信されると、それらを処理するためにより多くの子プロセスが生成されます。 HTTP リクエストが遅くなると、親プロセスはアイドル状態の子プロセスを強制終了し、他のプロセスのリソースを解放します。このスキームの利点は、Apache を非常に堅牢にすることです。子プロセスがクラッシュした場合でも、親プロセスと他の子プロセスはクラッシュした子プロセスから隔離されます。
プリフォークモデルは他の考えられる設計ほど高速ではありませんが、私にとっては「大した問題ではない」と思います。 Apache のパフォーマンスの問題が重大になるずっと前に、他のボトルネックが発生するため、PHP スクリプトを提供するサーバーでは実行できません。 Apache の堅牢性と信頼性はより重要です。
Apache 2.0 はマルチスレッド モードでの操作を提供します。私のベンチマークによると、このモードではパフォーマンス上の利点がほとんどありません。また、多くの PHP 拡張機能には互換性がないことにも注意してください (GD や IMAP など)。 Apache 2.0.47 (2003 年 10 月 21 日) でテスト済み。
Apache は httpd.conf ファイルを使用して設定されています。次のパラメータは、子プロセスの設定において特に重要です:
Directive
Default
Description
| MaxClients | 256 | |||||||||||||||||||||||||||||||||||||||||||||||||||
子プロセスの最大数作成する。デフォルトは、最大 256 の HTTP リクエストを同時に処理できることを意味します。それ以降の接続要求はキューに入れられます。
| StartServers | 5 | |||||||||||||||||||||||||||||||||||||||||||||||||||
起動時に作成する子プロセスの数。
| MinSpareServers | 5 | |||||||||||||||||||||||||||||||||||||||||||||||||||
作成する必要があるアイドル状態の子プロセスの数。アイドル状態の子プロセスの数がこの数を下回ると、最初に 1 つの子が作成され、次の 2 秒後に 2 つ、さらに 1 秒後に 4 つというように、1 秒あたり 32 の子が作成されるまで続きます。
| MaxSpareServers | 10 | |||||||||||||||||||||||||||||||||||||||||||||||||||
この数を超える子プロセスが生きている場合、これらの余分なプロセスは終了されます。
| MaxRequestsPerChild | 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||
子が終了するまでに処理できる HTTP リクエストの数を設定します。 0 に設定すると、決して終了しないことを意味します。メモリ リークが発生していると思われる場合、または十分に活用されていないリソースを解放するには、これを 100 ~ 10000 の値に設定します。
|
大規模なサイトの場合は、次の値に近い値が適している可能性があります: MinSpareServers 32 MaxSpareServers 64 Windows 上の Apache は動作が異なります。 Apache は子プロセスを使用する代わりにスレッドを使用します。上記のパラメータは使用されません。代わりに、デフォルトで 50 に設定される ThreadsPerChild という 1 つのパラメータがあります。このパラメータは、Apache によって生成されるスレッドの数を設定します。 Windows バージョンには子プロセスが 1 つしかないため、デフォルト設定の 50 は、50 個の同時 HTTP リクエストのみを処理できることを意味します。 Web サーバーのトラフィックが多い場合は、この値を 256 から 1024 まで増やしてください。 変更できるその他の便利なパフォーマンス パラメータには次のものがあります。
Web サイトのデフォルトの分離レベルを構成することもできます。 [アプリケーション保護] の [ホーム ディレクトリ] タブで、分離レベルを定義できます。高度に分離された Web サイトは、IIS とは別のプロセスとして実行されるため、実行が遅くなります。一方、IIS プロセスで Web サイトを実行するのは最も高速ですが、Web サイトのコードに重大なバグがある場合はサーバーがダウンします。現時点では、CGI を使用して PHP Web サイトを実行するか、アプリケーション保護を高に設定して ISAPI を使用することをお勧めします。 また、regedit.exe を使用して、次の場所に保存されている IIS 5 レジストリ設定を変更することもできます: HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesInetinfoParameters
このレジストリの場所に設定がない場合は、デフォルトが使用されます。 Windows での高いパフォーマンス: IIS と FastCGI 多くのテストを行った結果、IIS と FastCGI を使用することで Windows 上で最高の PHP パフォーマンスが得られることがわかりました。 CGI は、Web サーバーから外部プログラムを呼び出すためのプロトコルです。 CGI プログラムはページ要求のたびに終了するため、それほど高速ではありません。 FastCGI は、ページ リクエスト後に CGI プログラムを永続化し、新しいページ リクエストが来たときに同じ CGI プログラムを再利用することで、このプロトコルを変更して高パフォーマンスを実現します。 IIS での FastCGI のインストールは複雑であるため、EasyWindows を使用する必要があります。 PHPインストーラー。これにより、可能な限り最高のパフォーマンスを得るために、PHP、FastCGI、および Turck MMCache がインストールされます。このインストーラーは、Apache 1.3/2.0 用の PHP もインストールできます。 FastCGI に関するこのセクションは、2003 年 10 月 21 日に追加されました。 PHP4 の Zend エンジン Zend エンジンは、PHP4 で使用される内部コンパイラおよびランタイム エンジンです。 Zeev Suraski と Andi Gutmans によって開発された Zend Engine は、彼らの名前の略称です。 PHP4 の初期の頃は、次のように動作していました: PHP スクリプトは Zend Engine によってロードされ、Zend オペコードにコンパイルされました。オペコードは、オペレーション コードの略で、低レベルのバイナリ命令です。次に、オペコードが実行され、生成された HTML がクライアントに送信されました。オペコードは実行後にメモリからフラッシュされました。 現在、このプロセスを高速化するのに役立つ製品や技術が多数あります。次の図は、最新の PHP スクリプトがどのように動作するかを示しています。影付きのボックスはすべてオプションです。 PHP スクリプトはメモリにロードされ、Zend オペコードにコンパイルされます。これらのオペコードは、Zend Optimizer と呼ばれるオプションのピープホール オプティマイザーを使用して最適化できるようになりました。スクリプトに応じて、PHP コードの速度を 0 ~ 50% 向上させることができます。 以前は、実行後にオペコードは破棄されていました。現在は、オプションで、いくつかの代替オープンソース製品と商用クローズドソース製品である Zend Accelerator (旧名 Zend Cache) を使用して、オペコードをメモリにキャッシュできるようになりました。 Zend Optimizer と互換性のある唯一のオペコード キャッシュは、Zend Accelerator です。オペコード キャッシュは、スクリプトの読み込みとコンパイルの手順を削除することで実行を高速化します。オペコード キャッシュを使用すると、実行時間が 10 ~ 200% 改善されます。
高いパフォーマンスの秘訣の 1 つは、より高速な PHP コードを記述することではなく、生成された HTML をファイルまたは共有メモリにキャッシュすることで PHP コードの実行を回避することです。 PHP スクリプトは 1 回だけ実行されて HTML がキャプチャされ、その後のスクリプトの呼び出しではキャッシュされた HTML がロードされます。データを定期的に更新する必要がある場合、キャッシュされた HTML に有効期限値が設定されます。 HTML キャッシュは PHP 言語や Zend Engine の一部ではありませんが、PHP コードを使用して実装されます。これを行うクラス ライブラリは数多くあります。そのうちの 1 つは PEAR キャッシュです。これについては次のセクションで説明します。もう 1 つは、Smarty テンプレート ライブラリです。 最後に、Web クライアントに送信される HTML を圧縮できます。これを有効にするには、PHP スクリプトの先頭に次のコードを配置します。 76a91a73551d7a6df2440035cad9852a HTML の圧縮率が高い場合は、HTML ファイルのサイズを 50 ~ 80% 削減して、ネットワーク帯域幅の要件と遅延を軽減することができます。欠点は、圧縮のためにある程度の CPU パワーを確保する必要があることです。 PEAR キャッシュを使用した HTML キャッシュ PEAR キャッシュは、HTML や画像などの複数のタイプのデータをキャッシュできるようにする一連のキャッシュ クラスです。 PEAR キャッシュの最も一般的な用途は、HTML テキストをキャッシュすることです。これを行うには、start() 関数と end() 関数の間で出力またはエコーされたすべてのテキストをキャッシュする出力バッファリング クラスを使用します。 file", array("cache_dir" => "cache/") ); if ($contents = $cache->start(md5("これは一意のキーです!"))) { # # ああ、キャッシュされたデータが返されました# print $contents; # print "e388a4556c0f65e1904146cc1a846beeこれを持たずに家を出ないでください…94b3e26ee717c64999d7867364b1b4a3"; # キャッシュに配置します } Cache コンストラクターは、最初のパラメーターとして使用するストレージ ドライバーを受け取ります。ファイル、データベース、および共有メモリのストレージ ドライバーが利用可能です。 pear/Cache/Container ディレクトリを参照してください。 Ulf Wendel によるベンチマークは、「ファイル」ストレージ ドライバーが最高のパフォーマンスを提供することを示唆しています。 2 番目のパラメータはストレージ ドライバのオプションです。オプションは、キャッシュ ディレクトリの場所である「cache_dir」と、キャッシュされたすべてのファイルに使用するプレフィックスである「filename_prefix」です。奇妙なことに、キャッシュの有効期限はオプション パラメーターに設定されていません。 一部のデータをキャッシュするには、キーを使用してキャッシュされたデータの一意の ID を生成します。上の例では、md5("this is a unique key!") を使用しました。 start() 関数は、キーを使用してコンテンツのキャッシュされたコピーを検索します。コンテンツがキャッシュされていない場合、start() によって空の文字列が返され、end() が呼び出されるまで、以降のすべての echo() および print() ステートメントが出力キャッシュにバッファリングされます。 end() 関数はバッファの内容を返し、出力バッファリングを終了します。 end() 関数は、最初のパラメータとしてキャッシュの有効期限を受け取ります。このパラメータには、データをキャッシュする秒数、データの有効期限を指定する Unix 整数タイムスタンプ、またはデフォルトの 24 時間のゼロを指定できます。 PEAR キャッシュを使用する別の方法は、変数またはその他のデータを保存することです。 。これを行うには、基本 Cache クラスを使用できます: 18ae17aa53bcf926e898f010e2408154 ")キャッシュ/") ); $id = $cache->generateID("これは一意のキーです");if ($data = $cache->get($id)) { } else { $data = "慈悲の質は緊張しない..."; $cache->save($id, $data, $期限切れ = 60);print "キャッシュミス。0c6dc11e160d3b678d68754cc175188a"; } データを保存するにはsave()を使用します。一意のキーがすでに有効なファイル名である場合は、generateID() ステップをバイパスできます。 save() がデータをシリアル化するため、オブジェクトと配列を保存できます。最後のパラメータは、データの有効期限がいつ切れるかを制御します。これは、データをキャッシュする秒数、またはデータの有効期限が切れる日時を指定する Unix 整数タイムスタンプ、またはデフォルトの 24 時間を使用する場合はゼロにすることができます。キャッシュされたデータを取得するには get() を使用します。 $cache->delete($id) を使用してキャッシュされたデータ項目を削除し、$cache->flush() を使用してすべてのキャッシュされた項目を削除できます。新規:より高速なキャッシング クラスは Cache-Lite です。強くお勧めします。 ベンチマークの使用 前のセクションでは、パフォーマンスに関する多くの問題について説明しました。ここで、本質的な部分に移ります。つまり、何を調整すべきかについて適切な情報を取得できるように、コードの測定とベンチマークを行う方法です。 Web サーバー上で現実的なベンチマークを実行したい場合は、HTTP リクエストをサーバーに送信するツールが必要になります。 Unix では、ベンチマークを実行する一般的なツールには、Apache リリースの一部である ab (apachebench の略) と、新しいフラッド (httpd.apache.org/test/flood) が含まれます。 Windows NT/2000 では、Microsoft の無料の Web Application Stress Tool (webtool.rte.microsoft.com) を使用できます。 これらのプログラムは、複数の HTTP リクエストを同時に作成し、複数の Web クライアントをシミュレートし、リクエストの完了に関する詳細な統計を表示できます。 「vmstat 1」を使用して Unix 上でベンチマークを実行すると、サーバーがどのように動作するかを監視できます。これにより、ディスク I/O、仮想メモリ、CPU 負荷のパフォーマンスに関するステータス レポートが毎秒出力されます。あるいは、「top d 1」を使用すると、実行中のすべてのプロセスが CPU 負荷別にソートされて 1 秒ごとに全画面更新されます。 Windows 2000 では、パフォーマンス モニターまたはタスク マネージャーを使用してシステム統計を表示できます。 HTTP オーバーヘッドを気にせずにコードの特定の側面をテストしたい場合は、マイクロタイムを使用してベンチマークを実行できます。 () は、マイクロ秒単位の正確な現在時刻を文字列として返します。次の関数は、計算に適した数値に変換します。 function getmicrotime() { list($usec, $sec) =explode(" ",microtime()); # # ベンチマークコードはこちら # あるいは、APD や XDebug などのプロファイリング ツールを使用することもできます。 xdebug を使用してコードを圧縮する私の記事も参照してください。 ベンチマークのケーススタディ このケーススタディでは、クライアントのために行った実際のベンチマークについて詳しく説明します。この例では、顧客は、長い SQL クエリの実行を含まないすべての PHP ページに対して 5 秒の応答時間を保証したいと考えていました。次のサーバー構成が使用されました: Red Hat 7.2 Linux 上で PHP 4.0.6 を実行する Apache 1.3.20 サーバー。ハードウェアは、1 Gb の RAM を備えたツイン Pentium III 933 MHz の猛獣でした。 HTTP リクエストは、PHP スクリプト「testmysql.php」に対するものになります。このスクリプトは、別のサーバーで実行されている MySQL データベースから約 20 レコードを読み取り、処理します。簡単にするために、すべてのグラフィックスが別の Web サーバーからダウンロードされると仮定します。 ベンチマーク ツールとして「ab」を使用しました。 10 の同時接続 (-c10) を使用して、1000 のリクエスト (-n1000) を実行するように「ab」を設定します。結果は次のとおりです。 # ab -n1000 -c10 http://192.168.0.99/php/testmysql.phpThis is ApacheBench, Version 1.3Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Copyright (c) 1998-1999 The Apache Group, http://www.apache.org/Server Software: Apache/1.3.20Server Hostname: 192.168.0.99Server Port: 80Document Path: /php/testmysql.phpDocument Length: 25970 bytesConcurrency Level: 10Time taken for tests: 128.672 secondsComplete requests: 1000Failed requests: 0Total transferred: 26382000 bytesHTML transferred: 25970000 bytesRequests per second: 7.77Transfer rate: 205.03 kb/s receivedConnnection Times (ms) min avg maxConnect: 0 9 114Processing: 698 1274 2071Total: 698 1283 2185 ベンチマークの実行中、サーバー側でコマンド「top d 1」を使用してリソース使用率を監視しました。パラメータ「d 1」は、更新の間に 1 秒の遅延を意味します。出力を以下に示します。 10:58pm up 3:36, 2 users, load average: 9.07, 3.29, 1.7974 processes: 63 sleeping, 11 running, 0 zombie, 0 stoppedCPU0 states: 92.0% user, 7.0% system, 0.0% nice, 0.0% idleCPU1 states: 95.0% user, 4.0% system, 0.0% nice, 0.0% idleMem: 1028484K av, 230324K used, 798160K free, 64K shrd, 27196K buffSwap: 2040244K av, 0K used, 2040244K free 30360K cached PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND 1142 apache 20 0 7280 7280 3780 R 21.2 0.7 0:20 httpd 1154 apache 17 0 8044 8044 3788 S 19.3 0.7 0:20 httpd 1155 apache 20 0 8052 8052 3796 R 19.3 0.7 0:20 httpd 1141 apache 15 0 6764 6764 3780 S 14.7 0.6 0:20 httpd 1174 apache 14 0 6848 6848 3788 S 12.9 0.6 0:20 httpd 1178 apache 13 0 6864 6864 3804 S 12.9 0.6 0:19 httpd 1157 apache 15 0 7536 7536 3788 R 11.0 0.7 0:19 httpd 1159 apache 15 0 7540 7540 3788 R 11.0 0.7 0:19 httpd 1148 apache 11 0 6672 6672 3784 S 10.1 0.6 0:20 httpd 1158 apache 14 0 7400 7400 3788 R 10.1 0.7 0:19 httpd 1163 apache 20 0 7540 7540 3788 R 10.1 0.7 0:19 httpd 1169 apache 12 0 6856 6856 3796 S 10.1 0.6 0:20 httpd 1176 apache 16 0 8052 8052 3796 R 10.1 0.7 0:19 httpd 1171 apache 15 0 7984 7984 3780 S 9.2 0.7 0:18 httpd 1170 apache 16 0 7204 7204 3796 R 6.4 0.7 0:20 httpd 1168 apache 10 0 6856 6856 3796 S 4.6 0.6 0:20 httpd 1377 natsoft 11 0 1104 1104 856 R 2.7 0.1 0:02 top 1152 apache 9 0 6752 6752 3788 S 1.8 0.6 0:20 httpd 1167 apache 9 0 6848 6848 3788 S 0.9 0.6 0:19 httpd 1 root 8 0 520 520 452 S 0.0 0.0 0:04 init 2 root 9 0 0 0 0 SW 0.0 0.0 0:00 keventd "top" の出力を見ると、ツイン CPU の Apache サーバーはアイドル時間 0% でフル稼働しています。さらに悪いのは、負荷平均が過去 1 分間で 9.07 (過去 5 分間で 3.29、過去 15 分間で 1.79) であることです。負荷平均は、実行準備ができているプロセスの平均数です。ツイン プロセッサ サーバーの場合、負荷が 2.0 を超えると、システムが過負荷になっていることを意味します。負荷 (9.07) と ab. で定義した同時接続数 (10) の間に密接な関係があることに気づくかもしれません 幸いなことに、約 798,160 MB の空き容量があり、仮想メモリは使用されていないため、十分な物理メモリがあります。 . さらに下では、CPU 使用率の順にプロセスが表示されます。最もアクティブなものは Apache httpd プロセスです。最初の httpd タスクは 7280K のメモリを使用し、平均して CPU の 21.2%、物理メモリの 0.7% を占有しています。 STAT 列はステータスを示します。R は実行可能、S はスリープ中、W はプロセスがスワップアウトされていることを意味します。 Given the above figures, and assuming this a typical peak load, we can perform some planning. If the load average is 9.0 for a twin-CPU server and assuming each task takes about the same amount of time to complete, then a lightly loaded server should be 9.0 / 2 CPUs = 4.5 times faster. So a HTTP request that used to take 1.283 seconds to satisfy at peak load will take about 1.283 / 4.5 = 0.285 seconds to complete. To verify this, we benchmarked with 2 simultaneous client connections (instead of 10 in the previous benchmark) to give an average of 0.281 seconds, very close to the 0.285 seconds prediction! # ab -n100 -c2 http://192.168.0.99/php/testmysql.php [ some lines omitted for brevity ]Requests per second: 7.10Transfer rate: 187.37 kb/s receivedConnnection Times (ms) min avg maxConnect: 0 2 40Processing: 255 279 292Total: 255 281 332 Conversely, doubling the connections, we can predict that the average connection time should double from 1.283 to 2.566 seconds. In the benchmarks, the actual time was 2.570 seconds. Overload on 40 connections When we pushed the benchmark to use 40 connections, the server overloaded with 35% failed requests. On further investigation, it was because the MySQL server persistent connects were failing because of "Too Many Connections". The benchmark also demonstrates the lingering behavior of Apache child processes. Each PHP script uses 2 persistent connections, so at 40 connections, we should only be using at most 80 persistent connections, well below the default MySQL max_connections of 100. However Apache idle child processes are not assigned immediately to new requests due to latencies, keep-alives and other technical reasons; these lingering child processes held the remaining 20+ persistent connections that were "the straws that broke the Camel's back". The Fix By switching to non-persistent database connections, we were able to fix this problem and obtained a result of 5.340 seconds. An alternative solution would have been to increase the MySQL max_connections parameter from the default of 100. Conclusions The above case study once again shows us that optimizing your performance is extremely complex. It requires an understanding of multiple software subsystems including network routing, the TCP/IP stack, the amount of physical and virtual memory, the number of CPUs, the behavior of Apache child processes, your PHP scripts, and the database configuration. In this case the PHP code was quite well tuned, so the first bottleneck was the CPU, which caused a slowdown in response time. As the load increased, the system slowed down in a near linear fashion (which is a good sign) until we encountered the more serious bottleneck of MySQL client connections. This caused multiple errors in our PHP pages until we fixed it by switching to non-persistent connections. From the above figures, we can calculate for a given desired response time, how many simultaneous HTTP connections we can handle. Assuming two-way network latencies of 0.5 seconds on the Internet (0.25s one way), we can predict: As our client wanted a maximum response time of 5 seconds, the server can handle up to 34 simultaneous connections per second. This works out to a peak capacity of 34/5 = 6.8 page views per second. To get the maximum number of page views a day that the server can handle, multiply the peak capacity per second by 50,000 (this technique is suggested by the webmasters at pair.com, a large web hosting company), to give 340,000 page views a day. Code Optimizations The patient reader who is still wondering why so much emphasis is given to discussing non-PHP issues is reminded that PHP is a fast language, and many of the likely bottlenecks causing slow speeds lie outside PHP. Most PHP scripts are simple. They involve reading some session information, loading some data from a content management system or database, formatting the appropriate HTML and echoing the results to the HTTP client. Assuming that a typical PHP script completes in 0.1 seconds and the Internet latency is 0.2 seconds, only 33% of the 0.3 seconds response time that the HTTP client sees is actual PHP computation. So if you improve a script's speed by 20%, the HTTP client will see response times drop to 0.28 seconds, which is an insignificant improvement. Of course the server can probably handle 20% more requests for the same page, so scalability has improved. The above example does not mean we should throw our hands up and give up. It means that we should not feel proud tweaking the last 1% of speed from our code, but we should spend our time optimizing worthwhile areas of our code to get higher returns. High Return Code Optimizations The places where such high returns are achievable are in the while and for loops that litter our code, where each slowdown in the code is magnified by the number of times we iterate over them. The best way of understanding what can be optimized is to use a few examples: Example 1 配列を出力する簡単な例を 1 つ示します: for ($j=0; $j これは、コードを次のように変更することで大幅に高速化できます: for ($j=0, $max = sizeof($arr), $s = ''; $j $s .= $arr[$j]."0c6dc11e160d3b678d68754cc175188a"; echo $s; まず、式 $j 2 番目の問題は、PHP 4 では、複数回エコーする方が、すべてを文字列に保存して 1 回の呼び出しでエコーするよりも遅いということです。これは、エコーは HTTP クライアントへの TCP/IP パケットの送信を伴う可能性がある高価な操作であるためです。もちろん、$s に文字列を蓄積すると、より多くのメモリを使用するため、スケーラビリティの問題がいくつか発生します。そのため、ここにはトレードオフが関係していることがわかります。 上記のコードを高速化する別の方法は、出力バッファリングを使用することです。これにより、出力文字列が内部的に蓄積され、スクリプトの最後で出力が一度に送信されます。これにより、メモリの増加と遅延の増加を犠牲にして、ネットワークのオーバーヘッドが大幅に削減されます。完全に echo ステートメントで構成されたコードの一部では、15% のパフォーマンスの向上が観察されました。 ob_start(); ob_start() による出力バッファリングは、すべての PHP スクリプトのグローバル最適化として使用できることに注意してください。長時間実行されるスクリプトでは、出力バッファを定期的にフラッシュして、何らかのフィードバックが HTTP クライアントに送信されるようにすることもできます。これは ob_end_flush() で実行できます。この関数は出力バッファリングもオフにするため、フラッシュの直後に ob_start() を再度呼び出すことをお勧めします。 要約すると、この例では、ループの不変条件を最適化する方法と、出力バッファリングを使用してコードを高速化する方法を示しました。 例 2 次のコードでは、特別なフォーマット関数を使用して行をフォーマットし、PEAR DB レコードセットを反復処理し、結果をエコーします。今回は 10.2 ミリ秒で実行時間をベンチマークしました (これにはデータベース接続と SQL 実行時間は含まれません): function FormatRow(&$recordSet) for ($j = 0; $ j 6e2e54331a81701d054969b57d4f9506numRows(); $j++) { 例 1 から、コードを次のように変更することでコードを最適化できることがわかりました (実行時間: 8.7 ミリ秒): function FormatRow(&$recordSet) ob_start(); for ($j = 0, $max = $rs->numRows(); $ j < $max; $j++) { 私のベンチマークでは、$max を使用すると 0.5 ミリ秒、ob_start を使用すると 1.5 ミリ秒の高速化に寄与することがわかりました。ループアルゴリズムを変更すると、コードを簡素化して高速化できます。この場合、実行時間は 8.5 ミリ秒に短縮されます。 arr[1].72ac96585ae54b6ae11f849d2649d9e6'; One最後の最適化はここで可能です。関数呼び出しのオーバーヘッド (速度のために保守性が犠牲になる可能性があります) を削除して、さらに 0.1 ミリ秒 (実行時間: 8.4 ミリ秒) を短縮できます。 )) { } require_once("Cache/Output.php"); ob_start(); $cache = new Cache_Output("file", array("cache_dir) " => "cache/") ); if ($contents = $cache->start(md5("this is a unique kexy!"))) { print $contents; print "e388a4556c0f65e1904146cc1a846beeCache Miss94b3e26ee717c64999d7867364b1b4a3"; ## ## while ($arr = $rs->fetchRow()) { print $cache->end(100); print (getmicrotime()-$t); 以下に最適化方法をまとめます: 実行時間 (ミリ秒) 最適化方法 9.9 初期コード、最適化なし、データベース接続と SQL 実行時間を除く 3.頻繁に使用するメソッドを派生クラスに配置してみてください。 警告: PHP は継続的な改善プロセスを経ているため、将来状況が変わる可能性があります。 詳細 オブジェクト メソッド (クラスで定義された関数) の呼び出しは、通常の関数の約 2 倍遅いことがわかりました。呼び出します。私にとって、これは非常に許容できるものであり、他の OOP 言語と同等です。 メソッド内 (以下の比率はおおよその値です): 更新: 2004 年 7 月 11 日: 上記のテストは、約 3 年前に PHP 4.0.4 で行われました。これを PHP4.3.3 で再度テストしたところ、関数の呼び出しには約 20 の $localvar++ 操作が必要となり、メソッドの呼び出しには約 30 の $localvar++ 操作が必要になりました。これは、$localvar++ の実行速度が速くなったか、関数の速度が遅くなったことが原因である可能性があります。 調整の概要 2004 年 7 月 11 日更新: すべての関数呼び出しの実行プロファイルを使用してベンチマークを行うには、xdebug 拡張機能を試すことができます。 xdebug の使用方法の簡単なチュートリアルについては、「xdebug を使用したコードの圧縮」を参照してください。これを行うための市販の製品もあります。ゼンドスタジオ。 無駄な最適化 Some optimizations are useful. Others are a waste of time - sometimes the improvement is neglible, and sometimes the PHP internals change, rendering the tweak obsolete. Here are some common PHP legends: a. echo is faster than print Echo is supposed to be faster because it doesn't return a value while print does. From my benchmarks with PHP 4.3, the difference is neglible. And under some situations, print is faster than echo (when ob_start is enabled). b. strip off comments to speed up code If you use an opcode cache, comments are already ignored. This is a myth from PHP3 days, when each line of PHP was interpreted in run-time. c. 'var='.$var is faster than "var=$var" This used to be true in PHP 4.2 and earlier. This was fixed in PHP 4.3. Note (22 June 2004): apparently the 4.3 fix reduced the overhead, but not completely. However I find the performance difference to be negligible. Do References Speed Your Code? References do not provide any performance benefits for strings, integers and other basic data types. For example, consider the following code: And the same code without references: PHP does not actually create duplicate variables when "pass by value" is used, but uses high speed reference counting internally. So in TestRef(), $b and $c take longer to set because the references have to be tracked, while in TestNoRef(), $b and $c just point to the original value of $a, and the reference counter is incremented. So TestNoRef() will execute faster than TestRef(). In contrast, functions that accept array and object parameters have a performance advantage when references are used. This is because arrays and objects do not use reference counting, so multiple copies of an array or object are created if "pass by value" is used. So the following code: function ObjRef(&$o) is faster than: $function ObjRef($o) Note: In PHP 5, all objects are passed by reference automatically, without the need of an explicit & in the parameter list. PHP 5 object performance should be significantly faster. Many thanks also to Andrei Zmievski for reviewing this article. |