PHP array
是PHP的核心功能之一。它是稀疏的,允许同一数组中的多类型键,并支持集合,字典,数组,堆栈/队列和迭代功能。
但是在使用PHP一段时间之后,我发现很多array_*
函数比你初看起来慢得多。就像array_rand
在非常大的数组(10000+)上的情况一样。 array_rand
实际上是如此之慢,以至于在使用php数组作为索引数组的情况下,像rand( 0, array_length( $array ) - 1 )
这样的函数运行速度比array_rand
快。
现在问我的问题。
如何在C级别上实现PHP数组?这对于预测大量使用PHP数组数据类型的不同功能的函数的Big O非常有用。
答案 0 :(得分:34)
在读完zend / zend_hash.h和ext / standard / array.c之后我想我找到了答案(谢谢Chris和gumbo的建议)。
PHP数组是一个链式哈希表(在键冲突上查找O(c)和O(n)),它允许使用int和string键。它使用2种不同的散列算法将这两种类型拟合到相同的散列键空间中。此外,存储在散列中的每个值都链接到它之前存储的值和存储在(链接列表)之后的值。它还有一个临时指针,用于保存当前项,以便可以迭代哈希值。
array_rand
函数的捕获是为了确保密钥是真正随机的,array_rand
函数必须遍历数组rand(0, count($array))
次(O(n))。这是因为在O(c)时间内无法移动到散列表中的偏移量,因为无法保证该范围内没有丢失键。
这个发现让我感到有些困扰,因为这意味着PHP中没有数据类型具有正常的C数组特征。现在大部分时间都没问题,因为哈希查找速度非常快,但在array_rand
这样的情况下会出现错误。
另一件令我困扰的事情是array_key_exists
和in_array
的实施之间的差异。 array_key_exists
使用哈希查找(大部分时间为O(c))来查看密钥是否在数组中,而in_array
必须线性搜索哈希值(O(n))。
考虑以下两个例子:
in_array版本
$array = range(0, 100000);
if( in_array( $random_key, $array ) ) {
//we found a value
}
array_key_exists版本
$array = array_fill_keys( range(0, 100000), NULL );
if( array_key_exists( $random_key, $array ) ) {
//we found a value, err key
}
虽然in_array版本看起来更清晰,更容易理解,但它在大型数组(O(n))上实际上非常慢,其中array_key_exists(尽管是令人讨厌的长名称)非常快(O(c)或接近)。
总结:
我希望zend HashTable数据结构中有一个透明标志,在使用array_push
或array[] = $value
创建数组的情况下设置它,这将允许像C数组而不是链接一样进行扩展列表。
答案 1 :(得分:28)
PHP关联数组实际上是HashTables的实现。
在内部,可以制作数字数组或关联数组。 如果将它们组合在一起,它就是关联数组。
在数值数组中,它与C非常相似。您有指向ZVAL结构的指针数组。
因为指针具有固定长度(让我们称之为n),所以偏移(x)计算很容易:x * n。
在PHP类型中是ZVAL结构(因为它实现了动态类型),但它也有助于关联数组,因为你可以假设固定长度。因此,即使直接访问数组较慢,它仍然被认为是O(1)。
那么字符串键会发生什么? PHP使用哈希函数将它们转换为整数。
在数字和关联数组中搜索具有相似的效率,因为在内部它们都是数字。
由于附加级别(哈希函数),只能直接访问数组键。
答案 2 :(得分:5)
由于PHP数组are ordered maps(即使使用连续的整数索引)array_rand()
可能必须包含从中选择元素的键列表。我猜测缓存(经常无效的)密钥列表的空间和时间是无效的,因此每次调用都会产生至少O(n)遍历和建设成本。
因为您的rand(... length ...)
实现利用了您所拥有的特殊知识,即密钥是连续的整数,所以您将获得O(log n)查找成本。这似乎与Chacha102的数据一致。
答案 3 :(得分:2)
在文档中查看此注释,确认您的困境,即array_rand虽然对于小型数组来说很快,但扩展性很差。
我修改了fake_array_rand以便总是只返回1个元素,并且在调用array_rand时使用第二个参数作为1进行了一些基准测试。我为每个元素的数量运行了100个样本并获得了平均结果。虽然内部array_rand对于少量元素来说更快,但它的扩展性非常差。
1 elements: 2.0619630813599E-05 sec. for array_rand,8.4352493286133E-05 sec. for fake_array_rand 10 elements: 2.1675825119019E-05 sec. for array_rand,8.427619934082E-05 sec. for fake_array_rand 100 elements: 2.9319524765015E-05 sec. for array_rand,8.4599256515503E-05 sec. for fake_array_rand 1000 elements: 0.0001157283782959 sec. for array_rand,8.5572004318237E-05 sec. for fake_array_rand 10000 elements: 0.0016669762134552 sec. for array_rand,8.5201263427734E-05 sec. for fake_array_rand 100000 elements: 0.015599734783173 sec. for array_rand,8.5580348968506E-05 sec. for fake_array_rand 1000000 elements: 0.18011983394623 sec. for array_rand,8.6690187454224E-05 sec. for fake_array_rand <?php function fake_array_rand ($array) { $count = count ($array); # Help keep the number generator random :) $randval and usleep ("0.$randval"); # Seed the random number generator # Generate a random number srand ((double) microtime() * 10000000); $randval = rand(); # Use the random value to 'pick' an entry from the array # Count the number of times that the entry is picked ++$index[$randval % $count]; return $array[$randval % $count]; } ?>
答案 4 :(得分:2)
查看zend/zend_hash.c
和zend/zend_hash.h
我不知道c,但对我来说,它看起来像一个链式哈希表。