PHP函数的Big-O列表

时间:2010-03-18 23:12:32

标签: php performance algorithm arrays big-o

在使用PHP一段时间之后,我注意到并非所有内置PHP函数都如预期的那样快。考虑这两个可能的函数实现,它使用缓存的素数数组来查找数字是否为素数。

//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增长时,线性搜索O(n)会线性减慢。使用散列查找O(1)实现array_key_exists函数的情况除非哈希表得到极大填充(在这种情况下它只是O(n)),否则它不会减速。

到目前为止,我不得不通过反复试验来发现大O,偶尔也会looking at the source code。现在问题......

是否有所有内置PHP函数的理论(或实际)大O时间列表?

*或至少是有趣的

例如,我发现很难预测列出的大O函数,因为可能的实现取决于PHP的未知核心数据结构:array_mergearray_merge_recursivearray_reversearray_intersectarray_combinestr_replace(带数组输入)等。

4 个答案:

答案 0 :(得分:604)

因为在我认为将它作为某个地方参考之前,我觉得似乎没有人这样做过。我已经走了,无论是通过基准测试还是代码浏览来表征array_*函数。我试图将更有趣的Big-O放在顶部附近。此列表不完整。

注意:假设哈希查找计算的所有Big-O都是O(1),即使它实际上是O(n)。 n的系数很低,在查找Big-O的特性开始生效之前,存储足够大的数组的ram开销会对你造成伤害。例如,在N = 1和N = 1,000,000的array_key_exists调用之间的差异是〜50%的时间增加。

有趣点

  1. isset / array_key_existsin_arrayarray_search
  2. 快得多
  3. +(union)比array_merge快一点(看起来更好)。但它的确有不同的作用,所以请记住这一点。
  4. shufflearray_rand
  5. 位于同一Big-O层 由于重新索引处罚,
  6. array_pop / array_pusharray_shift / array_unshift更快
  7. <强>查找

    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,所有我)

    array_pop O(1)

    array_shift O(n) - 它必须重新索引所有键

    array_unshift O(n +Σvar_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,对于所有我) - 这是所有param_sizes的产物

    array_diff_key O(Σparam_i_size,对于i!= 1) - 这是因为我们不需要迭代第一个数组。

    array_merge O(Σray_i,i!= 1) - 不需要迭代第一个数组

    +(union)O(n),其中n是第二个数组的大小(即array_first + array_second) - 比array_merge开销少,因为它不需要重新编号

    array_replace O(Σarray_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(偏移+长度)或O(n)如果长度= NULL

    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)

    我要感谢Eureqa让我们很容易找到函数的Big-O。这是一个惊人的免费程序,可以找到任意数据的最佳拟合函数。

    编辑:

    对于那些怀疑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 );
    }
    

答案 1 :(得分:3)

您具体描述的案例的解释是关联数组实现为哈希表 - 因此按键查找(相应地,array_key_exists)是O(1)。但是,数组不按值索引,因此在一般情况下,发现数组中是否存在值的唯一方法是线性搜索。那里并不奇怪。

我认为没有关于PHP方法的算法复杂性的具体综合文档。但是,如果这是一个值得关注的问题,那么您可以随时查看the source code

答案 2 :(得分:3)

您几乎总是希望使用isset代替array_key_exists。我不是在看内部,但我很确定array_key_exists是O(N),因为它遍历数组的每个键,而isset尝试使用$search_array = array('first' => null, 'second' => 4); // returns false isset($search_array['first']); // returns true array_key_exists('first', $search_array); 来访问元素访问数组索引时使用的相同哈希算法。那应该是O(1)。

要注意的一个“问题”是:

<?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:

array_key_exists: 0.132308959961秒
{{1}} 2.33202195168秒

当然,这并没有显示时间复杂性,但它确实显示了两种功能相互比较的方式。

要测试时间复杂度,请比较在第一个键和最后一个键上运行其中一个函数所需的时间。

答案 3 :(得分:0)

如果人们在实践中遇到关键冲突而遇到麻烦,他们会使用辅助哈希查找或平衡树来实现容器。平衡树将给出O(log n)最坏情况行为和O(1)avg。 case(哈希本身)。在大多数实际的内存应用程序中,开销并不值得,但也许有一些数据库将这种形式的混合策略作为默认情况实现。