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

问题描述

| 我发现在检查数组引用中是否设置了键时,
array_key_exists
比than1ѭ慢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,$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,$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,$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,$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,$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。 键盘示例。     

解决方法

        在工作中,我有一个PHP虚拟机实例,其中包括一个称为VLD的PECL扩展。这使您可以从命令行执行PHP代码,而不是执行它,而是返回生成的操作码。 回答这样的问题真是太好了。 http://pecl.php.net/package/vld 万一您走了这条路(并且,如果您通常对PHP的内部运行方式感到好奇,我认为您应该这样做),则应绝对将其安装在虚拟机上(也就是说,我不会将其安装在计算机上)我正在尝试开发或部署到)。这是您用来唱歌的命令:
php -d vld.execute=0 -d vld.active=1 -f foo.php
查看操作码会告诉您一个更完整的故事,但是,我有一个猜测。...大多数PHP \内置组件都会复制Array / Object并对其执行操作(而不是复制- (立即写入)。最广为人知的示例是foreach()。当您将数组传递给foreach()时,PHP实际上正在制作该数组的副本并在该副本上进行迭代。这就是为什么通过将数组作为引用传递给foreach会看到显着的性能优势的原因: foreach($ someReallyBigArray as $ k =>&$ v) 但是这种行为-传递像这样的显式引用-对于foreach()是唯一的。因此,如果它使array_key_exists()的检查速度更快,我会感到非常惊讶。 好,回到我的意思。 大多数内置插件会获取数组的副本并对该副本执行操作。我要冒一个完全不合格的猜测,那就是isset()已高度优化,并且其中一种优化可能是在传入数组时不立即复制它。 我会尝试回答您可能遇到的任何其他问题,但您可能会在Google上读到很多“ \ zval_struct \”(这是PHP内部存储每个变量的数据结构。它是C结构) (以为关联数组),其键为\“ value \”,\“ type \”,\“ refcount \”。     ,        这是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,\"The first argument should be either a string or an integer\");
            RETURN_FALSE;
    }

}
    ,        不是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
仅从“ 8”部分删除了取消设置,这是经修改的部分,以供参考:
$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 );