23

我发现这array_key_existsisset检查是否在数组引用中设置了键慢了 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 上。

键盘示例

4

4 回答 4

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 as $k => &$v)

但是这种行为——像这样传递一个显式引用——是 foreach() 所独有的。因此,如果它使 array_key_exists() 检查更快,我会感到非常惊讶。

好的,回到我的意思..

大多数内置函数获取数组的副本并根据该副本进行操作。我将大胆猜测 isset() 是高度优化的,其中一个优化可能是在传入数组时不立即复制数组。

我会尝试回答您可能遇到的任何其他问题,但您可能会在 Google 上阅读很多“zval_struct”(这是 PHP 内部存储每个变量的数据结构。它是一个 C 结构(想想..关联数组),具有“值”、“类型”、“引用计数”等键。

于 2011-06-18T12:43:26.507 回答
3

这是 5.2.17 的 array_key_exists 函数的来源。您可以看到,即使键为空,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;
    }

}
于 2011-07-01T01:32:05.293 回答
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 )部分的unsetting,这是修改后的部分供参考:

$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 );
于 2011-06-14T00:50:47.520 回答
0

由于 PHP >= 7.4array_key_exists现在与isset.

https://www.php.net/manual/en/function.array-key-exists.php#125313

以及一些性能测试:

<?php declare(strict_types = 1);

function testPerformance($name, Closure $closure, $runs = 1000000)
{
    $start = microtime(true);
    for (; $runs > 0; $runs--)
    {
        $closure();
    }
    $end = microtime(true);

    printf("Function call %s took %.5f seconds\n", $name, $end - $start);
}

$items = [1111111];
for ($i = 0; $i < 100000; $i++) {
    $items[] = rand(0, 1000000);
}
$items = array_unique($items);
shuffle($items);

$assocItems = array_combine($items, array_fill(0, count($items), true));

$isset = function () use ($assocItems) {
    isset($items[1111111]);
};

$array_key_exists = function () use ($assocItems) {
    array_key_exists(1111111, $assocItems);
};

testPerformance('isset', $isset, 100000);
testPerformance('array_key_exists', $array_key_exists, 100000);

输出:

Function call isset took 0.00561 seconds
Function call array_key_exists took 0.00547 seconds
于 2022-02-23T18:24:04.837 回答