この質問は私のためだけのものです。安価な低速サーバー(または大量のトラフィックを持つサーバー)でも実行できる最適化されたコードを書くのが好きです。
私は周りを見回しましたが、答えが見つかりませんでした。私の場合の配列のキーは重要ではないことを念頭に置いて、これらの2つの例の間で何が速いのだろうと思っていました(当然のことながら擬似コード):
_<?php
$a = array();
while($new_val = 'get over 100k email addresses already lowercased'){
if(!in_array($new_val, $a){
$a[] = $new_val;
//do other stuff
}
}
?>
<?php
$a = array();
while($new_val = 'get over 100k email addresses already lowercased'){
if(!isset($a[$new_val]){
$a[$new_val] = true;
//do other stuff
}
}
?>
_
問題のポイントは配列の衝突ではないので、_$a[$new_value]
_の挿入の衝突を恐れている場合は、$a[md5($new_value)]
を使用できることを付け加えます。依然として衝突を引き起こす可能性がありますが、ユーザーが提供したファイルから読み取るときに起こりうるDoS攻撃を排除します( http://nikic.github.com/2011/12/28/Supercolliding-a-PHP-array .html )
これまでの答えはスポットオンです。この場合、isset
を使用すると、より高速になります。
in_array
は一致が見つかるまですべての値をチェックする必要があります。in_array
組み込み関数を呼び出すよりもオーバーヘッドが少なくなります。これらは、値を持つ配列(以下のテストでは10,000)を使用して、in_array
により多くの検索を行わせることで実証できます。
isset: 0.009623
in_array: 1.738441
これは、Jasonのベンチマークに基づいて構築され、いくつかのランダムな値を入力し、配列内に存在する値をときどき見つけます。すべてランダムなので、時間が変動することに注意してください。
$a = array();
for ($i = 0; $i < 10000; ++$i) {
$v = Rand(1, 1000000);
$a[$v] = $v;
}
echo "Size: ", count($a), PHP_EOL;
$start = microtime( true );
for ($i = 0; $i < 10000; ++$i) {
isset($a[Rand(1, 1000000)]);
}
$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;
$start = microtime( true );
for ($i = 0; $i < 10000; ++$i) {
in_array(Rand(1, 1000000), $a);
}
$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;
どちらが速いですか:
isset()
vsin_array()
isset()
は高速です。
明らかなはずですが、isset()
は単一の値のみをテストします。 in_array()
は配列全体を反復処理し、各要素の値をテストします。
microtime()
を使用すると、大まかなベンチマークは非常に簡単です。
Total time isset(): 0.002857
Total time in_array(): 0.017103
注:結果は、存在するかどうかに関係なく同様でした。
<?php
$a = array();
$start = microtime( true );
for ($i = 0; $i < 10000; ++$i) {
isset($a['key']);
}
$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;
$start = microtime( true );
for ($i = 0; $i < 10000; ++$i) {
in_array('key', $a);
}
$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;
exit;
以下もご覧になることをお勧めします。
isset()
を使用すると、 ハッシュテーブル が使用され、O(n)
検索が不要になるため、より高速なルックアップが利用されます。
最初に djb hash function を使用してキーがハッシュされ、O(1)
の同様にハッシュされたキーのバケットが決定されます。次に、O(n)
で正確なキーが見つかるまで、バケットが繰り返し検索されます。
意図的なハッシュ衝突 を除いて、このアプローチはin_array()
よりもはるかに優れたパフォーマンスをもたらします。
示した方法でisset()
を使用する場合、最終値を別の関数に渡すには、array_keys()
を使用して新しい配列を作成する必要があります。キーと値の両方にデータを保存することにより、メモリが侵害される可能性があります。
更新
コード設計の決定がランタイムのパフォーマンスにどのように影響するかを確認する良い方法は、スクリプトの コンパイル済みバージョン をチェックアウトすることです:
echo isset($arr[123])
_compiled vars: !0 = $arr
line # * op fetch ext return operands
-----------------------------------------------------------------------------
1 0 > ZEND_ISSET_ISEMPTY_DIM_OBJ 2000000 ~0 !0, 123
1 ECHO ~0
2 > RETURN null
_
echo in_array(123, $arr)
_compiled vars: !0 = $arr
line # * op fetch ext return operands
-----------------------------------------------------------------------------
1 0 > SEND_VAL 123
1 SEND_VAR !0
2 DO_FCALL 2 $0 'in_array'
3 ECHO $0
4 > RETURN null
_
in_array()
は比較的効率の悪いO(n)
検索を使用するだけでなく、関数(_DO_FCALL
_)として呼び出す必要もありますが、isset()
は単一のこのためのオペコード(_ZEND_ISSET_ISEMPTY_DIM_OBJ
_)。
2番目の方法は、特定の配列キーのみを検索し、検出されるまで配列全体を反復処理する必要がないため、より高速になります(検出されない場合はすべての配列要素を参照します)。