PHP関数のBig-Oのリスト


345

しばらくPHPを使用した後、組み込みのすべてのPHP関数が期待どおりに高速であるとは限りません。キャッシュされた素数の配列を使用して、数値が素数であるかどうかを検出する関数のこれら2つの可能な実装を検討してください。

//very slow for large $prime_array
$prime_array = array( 2, 3, 5, 7, 11, 13, .... 104729, ... );
$result_array = array();
foreach( $prime_array => $number ) {
    $result_array[$number] = in_array( $number, $large_prime_array );
}

//speed is much less dependent on size of $prime_array, and runs much faster.
$prime_array => array( 2 => NULL, 3 => NULL, 5 => NULL, 7 => NULL,
                       11 => NULL, 13 => NULL, .... 104729 => NULL, ... );
foreach( $prime_array => $number ) {
    $result_array[$number] = array_key_exists( $number, $large_prime_array );
}

これは、in_array$prime_array増加するにつれて線形的に減速する線形検索O(n)で実装されているためです。ここで、array_key_exists関数は、ハッシュテーブルは非常に移入されますない限り遅くしないであろうハッシュルックアップO(1)を用いて実施される(その場合、それが唯一のO(N))。

これまでのところ、試行錯誤を繰り返してBig-Oを発見し、たまにソースコードを確認する必要がありました。さて、質問です...

すべての組み込みPHP関数*の理論的な(または実用的な)ビッグO時間のリストはありますか?

*または少なくとも興味深いもの

:可能な実装は、PHPの未知のコアデータ構造に依存するため、例えば、私が記載されている機能の大きなOを予測することは非常に難しいそれを見つけるarray_mergearray_merge_recursivearray_reversearray_intersectarray_combinestr_replace(配列入力を備えた)、など


31
完全に話題外ですが、1は素数ではありません。
Jason Punyon

24
PHPの配列はハッシュテーブルです。それはあなたが知る必要があるすべてを教えてくれるはずです。ハッシュテーブルでキーを検索することはO(1)です。値の検索はO(n)です。これは、ソートされていないセットでは勝てません。気になる関数のほとんどは、おそらくO(n)です。:あなたが本当に知りたい場合はもちろん、あなたはソース読むことができますcvs.php.net/viewvc.cgi/php-src/ext/standard/...
フランク・ファーマー

11
記録としては、(値にNULLを使用する代わりに)を使用してtrue、を使用して存在をテストすることで、最速の実装を実現できますisset($large_prime_array[$number])。私の記憶が正しければ、in_array関数よりも何百倍も高速です。
mattbasta

3
Big Oの表記法は速度についてではありません。行動を制限することです。
Gumbo

3
@Kendallと比較していませんarray_key_exists、と比較していin_arrayます。in_array配列の各項目を反復処理し、その値を、渡された針と比較します。値をキーにフリップすると(そして各値をのようなダミー値に置き換えるだけでtrue、を使用するissetと何倍も速くなります。これは、配列のキーが(ハッシュテーブルのように)PHPによってインデックス付けされるためです。この方法でアレイを配置すると、速度が大幅に向上します
mattbasta 2010年

回答:


649

どこかで参照できるようにしておくのは良い考えだと思う前に、誰もこれをしたことがないようです。ただし、array_*関数を特徴付けるために、ベンチマークまたはコードスキミングのいずれかを使用しました。より興味深いBig-Oを一番上に配置しようとしました。このリストは完全ではありません。

注:実際にはO(n)ですが、ハッシュルックアップがO(1)であると想定して計算されたすべてのBig-O。nの係数が非常に低いため、十分な大きさの配列を格納することによるRAMオーバーヘッドは、ルックアップBig-Oの特性が有効になる前に害を及ぼします。たとえばarray_key_exists、N = 1とN = 1,000,000での呼び出しの違いは、最大50%の時間増加です。

興味深い点

  1. isset/ array_key_existsはるかに高速よりin_arrayarray_search
  2. +(ユニオン)は少し高速ですarray_merge(見栄えも良いです)。ただし、動作が異なるので注意してください。
  3. shuffle と同じBig-O層にある array_rand
  4. array_pop/ インデックスの再作成によるペナルティのためarray_pusharray_shift/ より速いarray_unshift

ルックアップ

array_key_existsO(n)ですが、O(1)に非常に近いです。これは、衝突での線形ポーリングが原因ですが、衝突の可能性は非常に小さいため、係数も非常に小さくなります。ハッシュルックアップをO(1)として扱い、より現実的なbig-Oを提供しているようです。たとえば、N = 1000とN = 100000の違いは、約50%の速度低下だけです。

isset( $array[$index] )O(n)ですが、O(1)に非常に近いです-array_key_existsと同じルックアップを使用します。言語構造であるため、キーがハードコードされている場合はルックアップをキャッシュし、同じキーが繰り返し使用される場合にスピードアップします。

in_array O(n)-これは、値が見つかるまで配列を線形検索するためです。

array_search O(n)-in_arrayと同じコア関数を使用しますが、値を返します。

キュー機能

array_push O(∑ var_i、すべてのiに対して)

array_pop O(1)

array_shift O(n)-すべてのキーのインデックスを再作成する必要があります

array_unshift O(n + ∑ var_i、for all i)-すべてのキーのインデックスを再作成する必要があります

配列の交差、ユニオン、減算

array_intersect_key 交差100%の場合O(Max(param_i_size)* ∑param_i_count、all i)、交差0%がO(∑param_i_size、all i)の場合

array_intersect 交差100%の場合、O(n ^ 2 * ∑param_i_count、すべてのiに対して)、交差0%がO(n ^ 2)と交差する場合

array_intersect_assoc 交差100%の場合O(Max(param_i_size)* ∑param_i_count、all i)、交差0%がO(∑param_i_size、all i)の場合

array_diff O(πparam_i_size、for all i)-これはすべてのparam_sizesの積です

array_diff_key O(∑ param_i_size、for i!= 1)-これは、最初の配列を反復処理する必要がないためです。

array_merge O(∑ array_i、i!= 1)-最初の配列を反復処理する必要はありません

+ (ユニオン)O(n)、ここでnは2番目の配列のサイズ(つまり、array_first + array_second)-番号を付け直す必要がないため、array_mergeよりもオーバーヘッドが少ない

array_replace O(∑ array_i、すべてのi)

ランダム

shuffle オン)

array_rand O(n)-線形ポーリングが必要です。

明白なBig-O

array_fill オン)

array_fill_keys オン)

range オン)

array_splice O(オフセット+長さ)

array_slice O(オフセット+長さ)または長さ= NULLの場合はO(n)

array_keys オン)

array_values オン)

array_reverse オン)

array_pad O(パッドサイズ)

array_flip オン)

array_sum オン)

array_product オン)

array_reduce オン)

array_filter オン)

array_map オン)

array_chunk オン)

array_combine オン)

関数のBig-Oを見つけやすくしてくれたEureqaに感謝します。任意のデータに最適な関数を見つけることができる驚くべき無料プログラムです。

編集:

PHPの配列ルックアップがO(N)であると疑う人のために、私はそれをテストするためのベンチマークを書きました(それらはまだO(1)ほとんどの現実的な値に対して効果的です)。

PHP配列ルックアップグラフ

$tests = 1000000;
$max = 5000001;


for( $i = 1; $i <= $max; $i += 10000 ) {
    //create lookup array
    $array = array_fill( 0, $i, NULL );

    //build test indexes
    $test_indexes = array();
    for( $j = 0; $j < $tests; $j++ ) {
        $test_indexes[] = rand( 0, $i-1 );
    }

    //benchmark array lookups
    $start = microtime( TRUE );
    foreach( $test_indexes as $test_index ) {
        $value = $array[ $test_index ];
        unset( $value );
    }
    $stop = microtime( TRUE );
    unset( $array, $test_indexes, $test_index );

    printf( "%d,%1.15f\n", $i, $stop - $start ); //time per 1mil lookups
    unset( $stop, $start );
}

5
@ケンドール:ありがとう!私は少し読んで、PHPが衝突のために「ネストされた」ハッシュテーブルを使用することがわかりました。つまり、衝突のためのlogn構造の代わりに、単に別のハッシュテーブルを使用します。そして、私は実際に言えば、PHPのハッシュテーブルがO(1)のパフォーマンス、または少なくとも平均してO(1)のパフォーマンスを与えることを理解しています。それがハッシュテーブルの目的です。なぜ「本当にO(logn)」ではなく、「本当にO(n)」であるとあなたが言ったのか、私はただ知りたがっていました。ちなみに投稿は素晴らしい!
カム

10
時間の複雑さはドキュメントに含まれるべきです!適切な機能を選択することで、時間を大幅に節約できます。または、予定していたことを実行しないように指示することもできます:pこのリストをありがとうございます。
サミュエル

41
私はこれが古いことを知っています...しかし、何ですか?その曲線は、O(n)をまったく示していません。en.wikipedia.org/ wiki / Logarithmの O(log n)を示しています。これは、ネストされたハッシュマップに期待することとも正確です。
Andreas 2013

5
配列の要素のunsetのBig-Oは何ですか?
チャンドリュー2014

12
ハッシュテーブルは確かに最悪の場合のO(n)ルックアップ複雑さを持っていますが、平均的なケースはO(1)であり、ベンチマークがテストしている特定のケースは、ゼロベースの連続した数値インデックスであるため、保証されています O(1)配列。ハッシュの衝突はありません。配列サイズへの依存が依然として見られる理由は、アルゴリズムの複雑さとは関係ありません。これは、CPUキャッシュの影響が原因です。配列が大きいほど、ランダムアクセスルックアップによってキャッシュミスが発生する可能性が高くなります(階層内でより高いキャッシュミス)。
NikiC、2016年

5

あなたが具体的に説明する場合の説明は、連想配列がハッシュテーブルとして実装されているということです-したがって、キー(そしてそれに対応するarray_key_exists)による検索はO(1)です。ただし、配列は値でインデックス付けされないため、配列に値が存在するかどうかを検出する一般的な方法は、線形検索のみです。そこに驚きはありません。

PHPメソッドのアルゴリズムの複雑さに関する特定の包括的なドキュメントはないと思います。ただし、その努力を正当化するのに十分な懸念がある場合は、いつでもソースコードを確認できます


これは本当に答えではありません。質問で述べたように、PHPのソースコードを調べてみました。PHPはCで実装されているため、複雑なマクロを使用してCで記述されているため、関数の基礎となる大きなOを「見る」ことが困難な場合があります。
ケンダルホプキンス

@Kendall私はソースコードに飛び込むというあなたの参照を見落としました。しかし、私の回答には答えがあります。「PHPメソッドのアルゴリズムの複雑さに関する具体的な包括的なドキュメントはないと思います。」「いいえ」は完全に有効な答えです。(c:
Dathan

4

ほとんどの場合、のisset代わりに使用したいですarray_key_exists。私は内部を見ていませんarray_key_existsが、issetアクセス時に使用されるのと同じハッシュアルゴリズムを使用して要素にアクセスしようとしながら、配列のすべてのキーを反復処理するため、これはO(N)であると確信しています。配列のインデックス。それはO(1)である必要があります。

注意が必要な「落とし穴」はこれです。

$search_array = array('first' => null, 'second' => 4);

// returns false
isset($search_array['first']);

// returns true
array_key_exists('first', $search_array);

私は興味があったので、違いをベンチマークしました:

<?php

$bigArray = range(1,100000);

$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
    isset($bigArray[50000]);
}

echo 'is_set:', microtime(true) - $start, ' seconds', '<br>';

$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
    array_key_exists(50000, $bigArray);
}

echo 'array_key_exists:', microtime(true) - $start, ' seconds';
?>

is_set:0.132308959961秒
array_key_exists:2.33202195168秒

もちろん、これは時間の複雑さを示していませんが、2つの関数が互いにどのように比較されるかを示しています。

時間の複雑さをテストするには、最初のキーと最後のキーでこれらの関数の1つを実行するのにかかる時間を比較します。


9
これは間違っています。array_key_existsが各キーを反復処理する必要がないと100%確信しています。信じられない場合は、以下のリンクをご覧ください。issetが非常に高速である理由は、それが言語構造であるためです。つまり、関数呼び出しのオーバーヘッドがありません。また、これが原因でルックアップをキャッシュしている可能性もあります。また、これは「質問」に対する答えではありません!PHP関数のBig(O)のリストが必要です(質問のとおり)。私の例の単一のベンチマークではありません。 svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/...
ケンドール・ホプキンス

それでも私を信じないなら、私はそのポイントを実証するために小さなベンチマークを作成しました。pastebin.com/BdKpNvkE
ケンダルホプキンス

ベンチマークの何が問題なのかは、xdebugを無効にする必要があることです。=)
ギルヘルムブランコ

3
array_key_existsでissetを使用する重要な理由は2つあります。まず、issetは、関数呼び出しのコストを軽減する言語構造です。これは$arrray[] = $appendvs array_push($array, $append)引数に似ています。次に、array_key_existsは、非設定値とnull値も区別します。for $a = array('fred' => null); array_key_exists('fred', $a)はtrueをisset($['fred'])返し、はfalseを返します。この追加の手順は簡単ではなく、実行時間が大幅に増加します。
シャチ

0

キーの衝突で実際に問題が発生した場合、セカンダリハッシュルックアップまたはバランスツリーを備えたコンテナーを実装します。バランスの取れたツリーは、O(log n)の最悪の場合の動作とO(1)の平均を与えます。ケース(ハッシュ自体)。オーバーヘッドは、メモリアプリケーションで最も実用的には価値がありませんが、おそらく、このような混合戦略をデフォルトのケースとして実装するデータベースがあるでしょう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.