从一次性能优化中引发的对in_array函数的思考 | 马犇-技术博客

从一次性能优化中引发的对in_array函数的思考

来源:本站原创 PHP, 原创 超过2,957 views围观 0条评论

最近优化了一个统计类的页面,里面用到了大量的in_array判断,通过在代码中分段计时来看,当数组中数据特别多或者调用特别频繁的时候,in_array函数的速度特别慢。遂看了一下in_array源码。
in_array内容实现如下:

/* void php_search_array(INTERNAL_FUNCTION_PARAMETERS, int behavior)
 * 0 = return boolean
 * 1 = return key
 */
static void php_search_array(INTERNAL_FUNCTION_PARAMETERS, int behavior) /* {{{ */
{
    zval *value,                /* value to check for */
         *array,                /* array to check in */
         **entry,               /* pointer to array entry */
          res;                  /* comparison result */
    HashPosition pos;           /* hash iterator */
    zend_bool strict = 0;       /* strict comparison or not */
    int (*is_equal_func)(zval *, zval *, zval * TSRMLS_DC) = is_equal_function;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "za|b", &value, &array, &strict) == FAILURE) {
        return;
    }

    if (strict) {
        is_equal_func = is_identical_function;
    }

    zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(array), &pos);
    while (zend_hash_get_current_data_ex(Z_ARRVAL_P(array), (void **)&entry, &pos) == SUCCESS) {
        is_equal_func(&res, value, *entry TSRMLS_CC);
        if (Z_LVAL(res)) {
            if (behavior == 0) {
                RETURN_TRUE;
            } else {
                zend_hash_get_current_key_zval_ex(Z_ARRVAL_P(array), return_value, &pos);
                return;
            }
        }
        zend_hash_move_forward_ex(Z_ARRVAL_P(array), &pos);
    }

    RETURN_FALSE;
}
/* }}} */

从代码中可以看出while循环在遍历数组中的所有元素取值做对比,如果数组中元素特别多又频繁调用的时候,效率可想而知。
由于数组的键是放在Hash表的key中的,Hash遍历会根据键的哈希值去查找,效率非常高,所以尝试了一上array_flip将键值对换,然后用isset去判断,效率呈指数级提高。
测试代码如下:


function getMillisecond() {
    list($t1, $t2) = explode(' ', microtime());
    return (float)sprintf('%.0f',(floatval($t1)+floatval($t2))*1000);
}

$array = [];

// Fill data
for ($i = 0; $i < 10000; $i++) {
    $array[] = $i;
}

$start_time = getMillisecond();

for ($i = 0; $i < 100000; $i++) {
    in_array(
        rand(0, 10000),
        $array
    );
}

echo '使用in_array占用时间:', (getMillisecond() - $start_time) . "毫秒\n";

$start_time = getMillisecond();
// 反转键和值
$array_flip = array_flip($array);
for ($i = 0; $i < 100000; $i++) {
    isset(
        $array_flip[rand(0, 10000)]
    );
}

echo '使用isset占用时间:', (getMillisecond() - $start_time) . "毫秒\n";

测试三次,结果如下:
php test.php
使用in_array占用时间:4893毫秒
使用isset占用时间:21毫秒

php test.php
使用in_array占用时间:4869毫秒
使用isset占用时间:20毫秒

php test.php
使用in_array占用时间:4863毫秒
使用isset占用时间:19毫秒

结果令人吃惊,所以建议在做统计等密集运算的时候,尽量不要使用in_array函数!

下篇文章:木有了,已经是最新文章