我发现array_key_exists
在检查是否在数组引用中设置了密钥时,比isset
慢了1000倍。是否有人了解PHP的实现方式可以解释为什么这是真的?
编辑: 我添加了另一个案例,似乎指出在使用引用调用函数时需要开销。
基准示例
function isset_( $key, array $array )
{
return isset( $array[$key] );
}
$my_array = array();
$start = microtime( TRUE );
for( $i = 1; $i < 10000; $i++ ) {
array_key_exists( $i, $my_array );
$my_array[$i] = 0;
}
$stop = microtime( TRUE );
print "array_key_exists( \$my_array ) ".($stop-$start).PHP_EOL;
unset( $my_array, $my_array_ref, $start, $stop, $i );
$my_array = array();
$start = microtime( TRUE );
for( $i = 1; $i < 10000; $i++ ) {
isset( $my_array[$i] );
$my_array[$i] = 0;
}
$stop = microtime( TRUE );
print "isset( \$my_array ) ".($stop-$start).PHP_EOL;
unset( $my_array, $my_array_ref, $start, $stop, $i );
$my_array = array();
$start = microtime( TRUE );
for( $i = 1; $i < 10000; $i++ ) {
isset_( $i, $my_array );
$my_array[$i] = 0;
}
$stop = microtime( TRUE );
print "isset_( \$my_array ) ".($stop-$start).PHP_EOL;
unset( $my_array, $my_array_ref, $start, $stop, $i );
$my_array = array();
$my_array_ref = &$my_array;
$start = microtime( TRUE );
for( $i = 1; $i < 10000; $i++ ) {
array_key_exists( $i, $my_array_ref );
$my_array_ref[$i] = 0;
}
$stop = microtime( TRUE );
print "array_key_exists( \$my_array_ref ) ".($stop-$start).PHP_EOL;
unset( $my_array, $my_array_ref, $start, $stop, $i );
$my_array = array();
$my_array_ref = &$my_array;
$start = microtime( TRUE );
for( $i = 1; $i < 10000; $i++ ) {
isset( $my_array_ref[$i] );
$my_array_ref[$i] = 0;
}
$stop = microtime( TRUE );
print "isset( \$my_array_ref ) ".($stop-$start).PHP_EOL;
unset( $my_array, $my_array_ref, $start, $stop, $i );
$my_array = array();
$my_array_ref = &$my_array;
$start = microtime( TRUE );
for( $i = 1; $i < 10000; $i++ ) {
isset_( $i, $my_array_ref );
$my_array_ref[$i] = 0;
}
$stop = microtime( TRUE );
print "isset_( \$my_array_ref ) ".($stop-$start).PHP_EOL;
unset( $my_array, $my_array_ref, $start, $stop, $i );
输出
array_key_exists( $my_array ) 0.0056459903717
isset( $my_array ) 0.00234198570251
isset_( $my_array ) 0.00539588928223
array_key_exists( $my_array_ref ) 3.64232587814 // <~ what on earth?
isset( $my_array_ref ) 0.00222992897034
isset_( $my_array_ref ) 4.12856411934 // <~ what on earth?
我使用的是PHP 5.3.6。
答案 0 :(得分:8)
在工作中,我有一个PHP的VM实例,其中包含一个名为VLD的PECL扩展。这使您可以从命令行执行PHP代码而不是执行它,而是返回生成的操作码。
回答这样的问题非常棒。
http://pecl.php.net/package/vld
以防万一你走这条路(如果你一般都很好奇PHP如何在内部工作,我想你应该)你应该把它安装在虚拟机上(也就是说,我不会把它安装在一个虚拟机上)机器我正在尝试开发或部署到)。这就是你用来唱歌的命令:
php -d vld.execute=0 -d vld.active=1 -f foo.php
查看操作码会告诉你一个更完整的故事,但是,我有一个猜测......大多数PHP的内置函数都会复制一个数组/对象并对该副本进行操作(而不是复制 - 在写,或者直接复制)。最广为人知的例子是foreach()。当您将数组传递给foreach()时,PHP实际上正在制作该数组的副本并在副本上进行迭代。这就是为什么你会通过将数组作为参考传递给foreach来看到显着的性能优势:
foreach($ someReallyBigArray为$ k =&gt;&amp; $ v)
但是这种行为 - 传递一个像这样的显式引用 - 对foreach()来说是唯一的。如果它更快地检查array_key_exists(),我会非常惊讶。
好的,回到我的目标......
大多数内置函数都会获取数组的副本并对该副本执行操作。我将冒险尝试完全无条件的猜测,即isset()已经过高度优化,其中一个优化可能是在传入时不立即复制数组。
我会尝试回答您可能遇到的任何其他问题,但您可能会阅读很多google的“zval_struct”(这是PHP内部存储每个变量的数据结构。它是一个C结构(想想) ..一个关联数组),其键有“value”,“type”,“refcount”等。
答案 1 :(得分:3)
这是5.2.17的array_key_exists函数的来源。您可以看到,即使密钥为null,PHP也会尝试计算哈希值。虽然有趣的是,如果你删除
// $my_array_ref[$i] = NULL;
然后它表现更好。必须有多个哈希查找。
/* {{{ proto bool array_key_exists(mixed key, array search)
Checks if the given key or index exists in the array */
PHP_FUNCTION(array_key_exists)
{
zval **key, /* key to check for */
**array; /* array to check in */
if (ZEND_NUM_ARGS() != 2 ||
zend_get_parameters_ex(ZEND_NUM_ARGS(), &key, &array) == FAILURE) {
WRONG_PARAM_COUNT;
}
if (Z_TYPE_PP(array) != IS_ARRAY && Z_TYPE_PP(array) != IS_OBJECT) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "The second argument should be either an array or an object");
RETURN_FALSE;
}
switch (Z_TYPE_PP(key)) {
case IS_STRING:
if (zend_symtable_exists(HASH_OF(*array), Z_STRVAL_PP(key), Z_STRLEN_PP(key)+1)) {
RETURN_TRUE;
}
RETURN_FALSE;
case IS_LONG:
if (zend_hash_index_exists(HASH_OF(*array), Z_LVAL_PP(key))) {
RETURN_TRUE;
}
RETURN_FALSE;
case IS_NULL:
if (zend_hash_exists(HASH_OF(*array), "", 1)) {
RETURN_TRUE;
}
RETURN_FALSE;
default:
php_error_docref(NULL TSRMLS_CC, E_WARNING, "The first argument should be either a string or an integer");
RETURN_FALSE;
}
}
答案 2 :(得分:1)
不是array_key_exists,但删除引用(= NULL)会导致这种情况。我从你的脚本中评论出来,这就是结果:
array_key_exists( $my_array ) 0.0059430599212646
isset( $my_array ) 0.0027170181274414
array_key_exists( $my_array_ref ) 0.0038740634918213
isset( $my_array_ref ) 0.0025200843811035
仅从array_key_exists( $my_array_ref )
部分删除了取消设置,这是修改后的部分供参考:
$my_array = array();
$my_array_ref = &$my_array;
$start = microtime( TRUE );
for( $i = 1; $i < 10000; $i++ ) {
array_key_exists( $i, $my_array_ref );
// $my_array_ref[$i] = NULL;
}
$stop = microtime( TRUE );
print "array_key_exists( \$my_array_ref ) ".($stop-$start).PHP_EOL;
unset( $my_array, $my_array_ref, $start, $stop, $i );