何が速いですか:in_arrayまたはisset?[閉まっている]


96

安い低速サーバー(または大量のトラフィックを持つサーバー)でも実行できる最適化されたコードを常に書きたいので、この質問は単に私にとってのものです。

周りを見回したが答えが見つからなかった。私の場合、配列のキーは重要ではない(当然のことながら擬似コードです)ことを念頭に置いて、これらの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)]。それでも衝突が発生する可能性がありますが、ユーザー提供のファイル(http://nikic.github.com/2011/12/28/Supercolliding-a-PHP-array.html)から読み取るときに、DoS攻撃の可能性を排除します


3
常に最適化されたコードを作成しようと努力している場合は、たまにプロファイラーを使用していますか?
マリオ

59
再開に投票します。質問は整形式であり、回答は事実と参照でサポートされています。一方でマイクロ -optimization、質問のこれらの種類がある建設
Jason McCreary、

5
@JasonMcCreary 2番目。もう1つ。
ジャック

7
これは何年も後のことですが、これをミクロの最適化とは考えていません。大きなデータセットの場合、それは大きな違いを生む可能性があります!!
Robert

2
...この質問は私には「建設的」に見えます。別の再開キャンペーンを開始します。
mickmackusa

回答:


117

これまでの答えは正解です。issetこの場合の使用は、

  • キーでO(1)ハッシュ検索を使用します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;

ハッシュについては知っていますが、関数を高速化するために可能な限り配列の値に対して同様の処理が行われないのはなぜだろうと思います。値にハッシュを追加するだけで同様の値を使用すると、メモリの消費量も減少します。
Fabrizio

3
@Fabrizio-配列値は複製され、ハッシュ不可能なオブジェクトを含むことができます。キーは一意である必要があり、簡単にハッシュ可能にする文字列と整数のみにすることができます。キーと値の両方をハッシュする1対1のマップを作成することもできますが、これはPHPの配列が機能する方法ではありません。
David Harkness

3
配列に一意の値が含まれていることが確実な場合は、別のオプション- フリップ+ issetがあります。
Arkadij Kuzhel

この例では、反転されたissetがin_arrayよりも高速であることに注意してください: `` `$ start = microtime(true); $ foo = array_flip($ a); for($ i = 0; $ i <10000; ++ $ i){isset($ foo [rand(1、1000000)]); } $ total_time = microtime(true)-$ start; echo "合計時間(フリップisset):"、number_format($ total_time、6)、PHP_EOL;
Andre Baumeier 2017年

@AndreBaumeierどちらが速いかは、配列のサイズと、実行するテストの数によって異なります。3つのテストを実行するために1万要素の配列を反転することは、おそらく効率的ではありません。
David Harkness 2017年

42

どちらが速いですか:isset()vsin_array()

isset() より速いです。

当然のことですが、isset()1つの値のみをテストします。一方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;

追加のリソース

以下もご覧になることをお勧めします。


素晴らしい解決策。より多くの人が、関数やコードを分割時間で使用しmicrotime()たり、他のツールを使用したりしないことに驚いています。信じられないほど貴重です。
nickhar 2012年

1
空の配列で同じキーを検索しin_arrayても、isset組み込み関数を使用するのではなく、関数を呼び出すオーバーヘッドが強調されるだけです。これは、ランダムなキーの束を含み、場合によっては既存のキー/値を検索する配列の方が適しています。
David Harkness

私がテストしていた間、私は、使用のベンチマークを行うとのmicrotimeかなり、私も実現whileし、foreach各リフレッシュで、私は別の「勝者」になったこと。それは常にあまりにも多くのサーバー変数に依存しており、最良の方法は、異なる時間に非常に多くの回数繰り返し、より頻繁に勝つものを取得するか、バックグラウンドで何が起こっているかを知り、それが最終的な勝者になることを知ることです。何があっても
ファブリツィオ

@David Harkness、あなたはすでに私の答えを抜粋しています。もっと欲しいのなら、私の肩の上に立って、あなた自身の答えを投稿してください。:)それにもかかわらず、関数のオーバーヘッドがに比べてすでにかなり高くisset()なっている場合、より大きな配列を渡すことでそれがより速くなると思うのはなぜですか?
Jason McCreary

1
@Fabrizio- ハッシュ関数ハッシュテーブルについて読んでください。
David Harkness

19

を使用isset()すると、ハッシュテーブルを使用するため検索が高速化され、O(n)検索の必要がなくなります。

最初にdjbハッシュ関数を使用してキーがハッシュされ、で同様にハッシュされたキーのバケットが決定されます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)を使用します。


7

2番目は、特定の配列キーのみを探し、それが見つかるまで配列全体を繰り返す必要がないため、より高速になります(見つからない場合はすべての配列要素を調べます)。


しかし、グローバルスコープで検索さ
el Dude

@ EL2002、その声明について詳しく説明していただけますか?
Fabrizio

1
マイク、isset()それが見つからない場合でも、配列全体を見ていないでしょうか?
Fabrizio

1
@Fabrizioいいえ、反復する必要はありません。内部的に(C言語では)PHP配列は単なるハッシュテーブルです。単一のインデックス値を検索するために、Cはその値のハッシュを作成し、メモリ内の割り当てられた場所を検索します。そこに値があるか、ありません。
Mike Brant

1
@Fabrizioこの記事では、Cで配列がPHPによって内部的にどのように表現されるかについての概要を説明します。 nikic.github.com/2012/03/28/...
マイク・ブラント
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.