web-dev-qa-db-ja.com

PHP関数のBig-Oのリスト

しばらく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が線形検索O(n)で実装されているためです。これは、$prime_arrayが大きくなると線形に遅くなります。 array_key_exists関数がハッシュルックアップO(1)で実装されている場合、ハッシュテーブルが極端に読み込まれない限りスローダウンしません(この場合はO(n)のみです)。

これまでのところ、私は試行錯誤を経てビッグOを発見しなければなりませんでしたが、ときどき ソースコードを見る を発見しました。それでは質問を...

すべての組み込みPHP関数の理論的(または実用的)な大きなOのリストはありますか?

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

たとえば、可能性のある実装は、PHPの未知のコアデータ構造array_mergearray_merge_recursivearray_reversearray_intersectarray_combinestr_replace(配列入力付き)などに依存するため、リストされている関数の大きなOを予測することは非常に困難です。

321
Kendall Hopkins

どこかで参照できるようにするのは良い考えだと思う前に、誰もこれをやったようには見えないので。 array_*関数を特徴付けるために、ベンチマークまたはコードスキミングを使用して行ってきました。私はもっ​​と面白いBig-Oをトップ近くに配置しようとしました。このリストは完全ではありません。

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

興味深い点

  1. isset/array_key_existsは、in_arrayおよびarray_searchよりもはるかに高速です。
  2. +(union)はarray_mergeよりも少し高速です(見栄えが良い)。ただし、動作が異なるため、留意してください。
  3. shufflearray_Randと同じBig-O層にあります
  4. array_pop/array_Pushは、インデックス再作成のペナルティにより、array_shift/array_unshiftよりも高速です。

Lookups

array_key_exists O(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、すべてのi)-すべてのキーのインデックスを再作成する必要があります

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

array_intersect_key交差点が100%の場合O(Max(param_i_size)* ∑param_i_count、すべてのi)、交差点0%が交差する場合O(∑param_i_size、すべての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、すべてのi)、交差点0%が交差する場合O(∑param_i_size、すべてのi)

array_diff O(πparam_i_size、すべてのi)-すべてのparam_sizeの積

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

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

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

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

ランダム

shuffle O(n)

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

明白なBig-O

array_fill O(n)

array_fill_keys O(n)

range O(n)

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

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

array_keys O(n)

array_values O(n)

array_reverse O(n)

array_pad O(pad_size)

array_flip O(n)

array_sum O(n)

array_product O(n)

array_reduce O(n)

array_filter O(n)

array_map O(n)

array_chunk O(n)

array_combine O(n)

関数のBig-Oを簡単に見つけられるように Eureqa に感謝します。任意のデータに最適な関数を見つけることができる驚くべきfreeプログラムです。

編集:

PHP配列検索がO(N)であると疑う人のために、私はそれをテストするためのベンチマークを作成しました(実際の値に対しては依然としてO(1)です)。

php array lookup graph

$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 );
}
612
Kendall Hopkins

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

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

5
Dathan

ほとんどの場合、array_key_existsの代わりにissetを使用します。内部を見ていないが、array_key_existsはO(N)であると確信している。なぜなら、isset配列インデックスにアクセスするときに使用されるものと同じハッシュアルゴリズムを使用して要素にアクセスしようとします。それはO(1)でなければなりません。

注意すべき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つの関数が互いにどのように比較されるかを示します。

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

3
ryeguy

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

0
Josh Stern