为什么array_key_exists比引用数组上的isset慢1000?

时间:2011-06-14 00:29:12

标签: php performance reference isset array-key-exists

我发现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。

Codepad example

3 个答案:

答案 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 );