在使用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_merge
,array_merge_recursive
,array_reverse
, array_intersect
,array_combine
,str_replace
(带数组输入)等。
答案 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%的时间增加。
有趣点:
isset
/ array_key_exists
比in_array
和array_search
+
(union)比array_merge
快一点(看起来更好)。但它的确有不同的作用,所以请记住这一点。shuffle
与array_rand
array_pop
/ array_push
比array_shift
/ array_unshift
更快<强>查找强>:
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)
。
$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(哈希本身)。在大多数实际的内存应用程序中,开销并不值得,但也许有一些数据库将这种形式的混合策略作为默认情况实现。