私は、伝統的な方法は誰にでも知られていると思います:
- SQLクエリの最適化。
- ボトルネックを検索して修正します。
- 頻繁に使用されるデータについては、Memcacheに切り替えます。
- APC、XCacheなどをインストールします。
- クライアントの最適化:CSSスプライトなど
私たちのプロジェクトでは、これはすべて行われましたが、同時に、ページ処理速度の問題が残っていました。 平均ページ処理速度は約500ミリ秒でした。 ある時点で、アイデアは、リソースとは何か、リソースは何に使うことができるかを分析するために思いつきました。
分析後、監視する必要がある次の主要なリソースが特定されました。
- CPU時間
- RAM
- 他のリソース(MySQL、memcache)の待機時間。
- ディスクのタイムアウト。
このプロジェクトでは、ディスクへの書き込みをほとんど使用しないため、4番目の項目はすぐに削除されました。
その後、ボトルネックの検索が始まりました。
ステージ1
最初に試みられたのは、MySQLクエリのプロファイリングで、遅いクエリを検索することでした。 このアプローチはあまり成功しませんでした。いくつかのリクエストは最適化されましたが、平均ページ処理時間はそれほど変化しませんでした。
ステージ2
次は、XHProfでコードをプロファイリングする試みでした。 これにより、ボトルネックが加速し、負荷を約10〜15%削減できました。 しかし、これも主な問題を解決しませんでした。 (興味深い場合は、XHProfを使用して最適化する方法に関する記事を個別に作成できます。コメントでお知らせください。)
ステージ3
第3段階では、リクエストを処理するのにどれだけのメモリが必要かを考えるようになりました。 これが問題であることが判明しました-単純なリクエストでは、最大20MBのコードをRAMにロードする必要があります。 データベースのクエリや大きなファイルのダウンロードを行わずにページを簡単にロードできるため、この理由は明確ではありませんでした。
アナライザーを作成し、各PCPファイルがオンになったときに必要なメモリー量を調べることが決定されました。
アナライザーは非常にシンプルです。プロジェクトには既にファイルオートローダーがあり、クラス名に基づいて、必要なファイル(オートロード)自体をロードしました。 ファイルをダウンロードする前のメモリ容量とダウンロードしたメモリ容量の2行を追加しました。
コード例:
Profiler::startLoadFile($fileName); Include $fileName; Profiler::endLoadFile($fileName);
最初の行は最初のメモリの量を保存し、最後の行は差分を減算してダウンロードリストに追加します。 プロファイラーは、ファイルダウンロードキュー(バックトラック)も保存します。
すべてのコードの実行の最後に、収集した情報とともにパネルの出力を追加しました。
echo Profiler:showPanel();
分析では、過剰なメモリを使用できる非常に興味深い理由が示されました。 すべてのページを分析し、余分なファイルのロードを削除した後、ページに必要なメモリは8 MB未満になりました。 ページの更新が速くなり、サーバーの負荷が減少し、同じマシンでより多くのクライアントを処理できるようになりました。
次は、変更されたもののリストです。 ここで、機能自体は変更されていないことに注意してください。 コード構造のみが変更されています。
元のプロジェクトコードを提供する方法がないため、すべての例は特別に簡略化されています。
例1:大きな親クラスを使用するがロードしない
例:
class SysPage extends Page{ static function helloWorld(){ echo "Hello World"; } }
ファイル自体は非常に小さいですが、同時に、少なくとも1つの他のファイルをプルします。これは非常に大きくなる可能性があります。
いくつかの解決策があります。
- 不要な継承を削除します。
- すべての親クラスのサイズが最小であることを確認してください。
例2:長いクラスを使用しますが、クラスのごく一部しか必要ありません
前の段落と非常に似ています。 例:
class Page{ public static function isActive(){} // 1000-2000 }
当然、PHPは、必要な関数が1つだけであることを知りません。 このクラスにアクセスするとすぐに、ファイル全体がロードされます。
解決策:
同様のクラスがある場合は、別のクラスでどこでも使用されている機能を強調する価値があります。 現在のクラスでは、互換性のために新しいクラスを参照できます。
例3:定数のみの大きなクラスをロードする
例:
$CONFIG['PAGES'] = array( 'news' => Page::PAGE_TYPE1, 'about' => Page::PAGE_TYPE2, );
または:
if(get('page')==Page::PAGE_TYPE1){}
つまり この例は前のものと似ていますが、現在は定数のみです。 解決策は前のケースと同じです。
例4:使用できないクラスのコンストラクターでの自動作成
例:
class Action{ public $handler; public function __construct(){ $this->handler = new Handler(); } public function handle1(){} public function handle2(){} public function handle3(){} public function handle4(){} public function handle5(){} public function handle6(){} public function handle7(){} public function handle8(){} public function handle9(){} public function handle10(){ $info = $this->handler->getInfo(); } }
ハンドラが頻繁に使用される場合、問題はありません。 まったく異なる質問は、20の機能のうち1つだけで使用されるかどうかです。
解決策:
コンストラクターから削除するか、マジック__getメソッドを使用して遅延読み込みに切り替えます。または、たとえば次のようにします。
public function handle10(){ $info = $this->handler()->getInfo(); } public function handler(){ if($this->handler===null) $this->handler = new Handler(); return $this->handler; }
例5:不要な言語ファイル/構成のアップロード
例:すべてのメニュー項目のすべてのページの設定を含む大きなファイルがあります。 ファイルには多くの配列を含めることができます。
データの一部がめったに使用されず、一部が頻繁に使用される場合、このファイルは分離の候補です。
つまり これらの設定は、必要な場合にのみロードする価値があります。
メモリーの使用例:
16 kbのファイル、単なるデータの配列-100 kb以上が必要です。
例6:serialize / unserializeの使用
頻繁に変更される設定のいくつかは、シリアル化形式でファイルに保存しました。 これにより、プロセッサとRAMの両方がロードされることがわかりました。PHPバージョン5.3.xでは、unserialize関数のメモリリークが非常に強いことがわかりました。
最適化の後、これらの関数を可能な限り削除しました。 var_exportで保存された配列としてファイルにデータを保存し、include / requireを使用してデータをロードすることにしました。 したがって、APCモードを使用することができました。
残念ながら、memcacheに保存されたデータの場合、このアプローチは機能しません。
まとめ
これらの例はすべて、プロジェクトの構造をあまり変更することなく、見つけやすく、非常に簡単に編集できます。 「100 kb以上を必要とするファイルは、ダウンロードを最適化できるかどうかを確認する必要があります。 また、同じブランチから複数のファイルがダウンロードされた状況も調べました。 この状況では、ファイルブランチ全体をまったくダウンロードしないかどうかを検討しました。 重要な考えは、「ダウンロードするものはすべて意味があるはずです。 何らかの方法でダウンロードできない場合-ダウンロードしない方が良いです。
おわりに
上記のすべてを削除した後、サーバーへの1つの要求で必要なRAMが約2倍少なくなりました。 これにより、サーバーを再構成し、1ページを数回ロードする平均速度を低下させることなく、プロセッサーの負荷を約2倍削減できました。
今後の計画
興味深い場合は、XHprofを使用してコードをプロファイリングし、ボトルネックを見つける方法を説明する計画があります。