PHP のパフォーマンスは常に向上しています。ただし、不適切に使用したり、注意を怠ったりすると、PHP の内部実装の落とし穴に陥る可能性があります。数日前にパフォーマンスの問題が発生しました。
これが起こりました。私たちのインターフェイスの 1 つが戻るのに毎回 5 秒かかったと同僚が報告しました。私たちは一緒にコードを確認し、読み取りキャッシュがループ内で呼び出されていることに「驚きました」。動作しましたが、キャッシュされたキーは変更されていないため、このコードをループの外に移動して、再度テストすると、インターフェイスの戻り時間が 2 秒に短縮されました。倍増しましたが、明らかに受け入れられる結果ではありません。
パフォーマンスの問題を引き起こしたコードの量はそれほど多くなかったので、IO の問題を解決した後、テスト コードを作成しましたが、案の定、すぐに問題が再発しました。
<?php $y="1800"; $x = array(); for($j=0;$j<2000;$j++){ $x[]= "{$j}"; } for($i=0;$i<3000;$i++){ if(in_array($y,$x)){ continue; } } ?>
shell$ time /usr/local/php/bin/php test.php
real 0m1.132s
user 0m1.118s
sys 0m0.015s
はい、文字列番号を使用しています。これは次のようになります。キャッシュから取り出されたときのように!そこで、ここでは特別に文字列に変換します(直接数値であればこの問題は発生しません。自分で確認できます)。消費される時間は 1 秒で、これはわずか 3000 サイクルであることがわかります。また、その後のシステム時間も strace を使用しても有効な情報が得られないことがわかります。
shell$ strace -ttt -o xxx /usr/local/php/bin/php test.php
shell$less xxx
これら 2 つのシステムコール間の遅延が非常に大きいことだけがわかります。何をしたのか分かりませんか?幸いなことに、Linux のデバッグ ツールには strace に加えて ltrace も含まれています (もちろん、dtrace や ptrace もありますが、これらはこの記事の範囲外なので省略します)。
引用: strace はプロセスのシステムコールまたはシグナル生成を追跡するために使用され、ltrace はライブラリ関数を呼び出すプロセスを追跡するために使用されます (IBM 開発者ワークス経由)。
干渉要因を排除するために、結果に影響を与える過剰な malloc 呼び出しを避けるために、$x を array("0","1","2",…) の形式に直接割り当てます。
shell$ ltrace -c /usr/local/php/bin/php test.php を実行します
図 2
ライブラリ関数 __strtol_internal が非常に頻繁に呼び出され、94% に達していることがわかります。このライブラリ関数 __strtol_internal が何をするのか調べてみると、これは strtol のエイリアスであることが分かりました。これは、PHP エンジンがこれが文字列型であることを検出したと推測できます。この変換プロセスは時間がかかりすぎます。
shell$ ltrace -e "__strtol_internal" /usr/local/php/bin/php test.php
この時点で、次の図のように大量の呼び出しを簡単にキャッチできます。 in_array 比較のため、2つの文字列をlong integer型に変換してから比較しますが、これでパフォーマンスが消費されるのかは分かりません。
問題の核心はわかったので、解決策はたくさんあります。最も簡単な方法は、in_array の 3 番目のパラメーターを true に追加することです。これは、厳密な比較となり、型が同時に比較されることを意味します。これにより、PHP の型変換が非常に高速になるのを回避できます。コードは次のとおりです。 ! ! sys にかかる時間はほとんど変わっていないことがわかります。もう一度 ltrace してみましょう。malloc 呼び出しの干渉を排除するために $x を直接割り当てる必要があります。実際のアプリケーションではキャッシュから一度に取得するため、サンプル コードのような適用するループはありません。思い出のために。
もう一度実行してください<?php $y="1800"; $x = array(); for($j=0;$j<2000;$j++){ $x[]= "{$j}"; } for($i=0;$i<3000;$i++){ if(in_array($y,$x,true)){ continue; } } ?>
__ctype_to lower_loc が最も時間がかかります。ライブラリ関数 __ctype_to lower_loc の機能を確認しました。簡単に理解すると、文字列を小文字に変換することになります。つまり、in_array の比較文字列では大文字と小文字が区別されないということですか?実際、この関数呼び出しは in_array とほとんど関係がありません。in_array の実装については、PHP のソース コードを見たほうがよく理解できると思います
夕方、以下を読みました。 PHP 5.4.10 のソース コードは非常に興味深いものです (笑)。./ext/standard/array.c の 1248 行目にあり、以下の array_serach もこれを調整していることがわかります。パラメータが違います!いくつかの追跡の後、in_array の緩い比較の場合、彼は最終的に ./Zend/zend_operators.c にある関数 zendi_smart_strcmp (実際には「スマート」関数) を比較のために呼び出しました。 ltrace を使用して、大量のキャプチャされたデータを変換しました。整数への変換は is_numeric_string_ex の動作です。
関数 is_numeric_string_ex は ./Zend/zend_operators.h で定義されています。一連の判断と変換の後、232 行目で strtol が呼び出されます。これは、記事で説明した文字列を長い文字列に変換するシステム関数です。整数、写真と真実があります
PHP の in_array の低パフォーマンスの問題に関連するその他の記事については、PHP 中国語 Web サイトに注目してください。