PHP函数的Big-O列表

Ken*_*ins 332 php arrays algorithm performance 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 );
}
Run Code Online (Sandbox Code Playgroud)

这是因为in_array是用线性搜索O(n)实现的,线性搜索O(n)随着in_array增长线性减慢.其中$prime_array函数是使用散列查找O(1)实现的,除非哈希表得到极大填充(在这种情况下它只是O(n)),否则它不会减慢速度.

到目前为止,我必须通过反复试验来发现big-O,并偶尔查看源代码.现在问题......

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

*或至少是有趣的

例如,发现很难预测列出的大O函数是什么,因为可能的实现取决于PHP的未知核心数据结构:array_merge,array_merge_recursive,array_reverse,array_intersect,array_combine,str_replace(带数组输入)等.

Ken*_*ins 626

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

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

有趣的点:

  1. isset/ array_key_existsin_array和快得多array_search
  2. +(联盟)比array_merge(并且看起来更好)快一点.但它的确有不同的作用,所以请记住这一点.
  3. shuffle 与...相同的Big-O等级 array_rand
  4. array_pop/ array_push快于array_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,对于所有我)

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,对于所有我) - 这是所有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 上)

array_rand O(n) - 需要线性轮询.

明显的大O:

array_fill 上)

array_fill_keys 上)

range 上)

array_splice O(偏移+长度)

array_slice 如果length = NULL,则为O(偏移+长度)或O(n)

array_keys 上)

array_values 上)

array_reverse 上)

array_pad O(pad_size)

array_flip 上)

array_sum 上)

array_product 上)

array_reduce 上)

array_filter 上)

array_map 上)

array_chunk 上)

array_combine 上)

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

编辑:

对于那些怀疑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 );
}
Run Code Online (Sandbox Code Playgroud)

  • 我知道这很老了......但是什么?该曲线根本不显示O(n),它显示O(log n),http://en.wikipedia.org/wiki/Logarithm.对于嵌套哈希映射的预期,这也是准确的. (38认同)
  • 虽然哈希表确实具有最坏情况的O(n)查找复杂度,但是平均情况是O(1),并且您的基准测试的特定情况甚至是*保证*O(1),因为它是基于零的,连续的,数字的-indexed数组,永远不会有哈希冲突.您仍然看到依赖于数组大小的原因与算法复杂性无关,它是由CPU缓存效应引起的.数组越大,随机访问查找越有可能导致缓存未命中(并且层次结构中的缓存未命中率更高). (12认同)
  • 时间复杂性应该包含在文档中!选择正确的功能可以节省您这么多时间,或者告诉您避免按照您的计划行事:p感谢您的名单! (10认同)
  • @Kendall:谢谢!我做了一些阅读,结果发现PHP使用'嵌套'哈希表进行冲突.也就是说,它不是用于冲突的logn结构,而是使用另一个哈希表.我确实理解,实际上PHP哈希表给出了O(1)性能,或平均至少为O(1) - 这就是哈希表的用途.我只是好奇为什么你说他们"真的是O(n)"而不是"真的是O(logn)".好的文章! (5认同)
  • 在数组元素上取消设置的Big-O是什么? (4认同)
  • @Cam Big-O 是 N-&gt;infitity 时函数的上界。虽然该函数在“填充阶段”具有“O(1)”和“O(log(N))”,但它最终稳定在“O(N)”。 (2认同)
  • @Kendall:在最坏的情况下,它确实具有 O(n) 性能。但我认为平均情况下的性能仍然是 O(1)。要看到这一点:当发生冲突时,会为该键上的所有值创建一个新的哈希表,并在那里重新对键进行哈希处理。那里再次发生碰撞的可能性很小。当我们添加越来越深的哈希表来处理冲突时,我们实际上讨论的是越来越小的输入子集,因为这些嵌套冲突很少发生。因此,平均情况下的运行时间是 O(1),而不是具有小常数的 O(n)。 (2认同)
  • 仅供参考,PHP 7 对数组使用[大大改进的哈希表实现](https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html)。 (2认同)

rye*_*guy 6

您几乎总是想使用isset而不是array_key_exists. 我没有查看内部结构,但我很确定这array_key_exists是 O(N),因为它会迭代数组的每个键,同时isset尝试使用访问时使用的相同哈希算法来访问元素数组索引。那应该是 O(1)。

需要注意的一个“陷阱”是:

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

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

// returns true
array_key_exists('first', $search_array);
Run Code Online (Sandbox Code Playgroud)

我很好奇,所以我对差异进行了基准测试:

<?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';
?>
Run Code Online (Sandbox Code Playgroud)

is_set:0.132308959961 秒
array_key_exists:2.33202195168 秒

当然,这并没有显示时间复杂度,但它确实显示了这两个函数如何相互比较。

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

  • 这是错误的。我 100% 确定 array_key_exists 不必迭代每个键。如果您不相信,请查看下面的链接。isset 速度如此之快的原因是它是一种语言构造。这意味着它没有执行函数调用的开销。另外,我认为它可能会缓存查找,因此。另外,这不是问题的答案!我想要 PHP 函数的 Big(O) 列表(如问题所述)。我的例子没有一个基准。http://svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/standard/array.c (11认同)
  • 您想要使用 isset 而不是 array_key_exists 有两个关键原因。首先,isset 是一种降低函数调用成本的语言结构。这类似于“$array[] = $append”与“array_push($array, $append)”参数。其次,array_key_exists 还区分非设置值和空值。对于 `$a = array('fred' =&gt; null);` `array_key_exists('fred', $a)` 将返回 true,而 `isset($['fred'])` 将返回 false。这个额外的步骤并不简单,并且会大大增加执行时间。 (4认同)

Dat*_*han 5

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

我认为没有关于PHP方法算法复杂性的具体综合文档。但是,如果您有足够大的忧虑需要付出努力,则可以随时查看源代码