Почему array_key_exists 1000x медленнее, чем isset на ссылочных массивах?
Я обнаружил, что array_key_exists
более 1000 раз медленнее, чем isset
при проверке, установлен ли ключ в ссылке массива. Кто-нибудь, кто понимает, как реализуется PHP, объясняет, почему это верно?
EDIT:
Я добавил еще один случай, который, по-видимому, указывает на то, что он необходим для вызова функций со ссылкой.
Пример теста
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.
Ответы
Ответ 1
На работе у меня есть экземпляр виртуальной машины PHP, который включает расширение PECL под названием VLD. Это позволяет вам выполнять PHP-код из командной строки и вместо этого выполнять его, вместо этого он генерирует сгенерированный код операции.
Это блестяще отвечает на такие вопросы.
http://pecl.php.net/package/vld
На всякий случай вы идете по этому маршруту (и если вам вообще интересно, как работает PHP внутри, я думаю, вам следует), вы должны обязательно установить его на виртуальной машине (то есть я бы не установил его на машина, которую я пытаюсь разработать или развернуть). И это команда, которую вы будете использовать, чтобы заставить ее петь:
php -d vld.execute=0 -d vld.active=1 -f foo.php
Глядя на коды операций, мы расскажем вам более полную историю, однако у меня есть предположение.... Большинство встроенных PHP-модулей делают копию массива/объекта и действуют на эту копию (а не копируют- on-write либо, немедленная копия). Наиболее широко известный пример этого - foreach(). Когда вы передаете массив в foreach(), PHP фактически создает копию этого массива и выполняет итерацию на копии. Вот почему вы увидите значительное преимущество в производительности, передав массив в качестве ссылки в foreach следующим образом:
foreach ($ someReallyBigArray как $k = > & $v)
Но это поведение - то, что передается в явной ссылке вроде этого - уникально для foreach(). Поэтому я был бы очень удивлен, если бы он сделал array_key_exists() проверкой быстрее.
Хорошо, обратно к тому, что я получаю..
Большинство встроенных модулей берут копию массива и действуют на эту копию. Я собираюсь начать совершенно безоговорочное предположение, что isset() сильно оптимизирован и что одна из этих оптимизаций, возможно, не делает немедленную копию массива при его передаче.
Я постараюсь ответить на любые другие вопросы, которые могут возникнуть у вас, но вы, вероятно, могли бы прочитать много из вас в google для "zval_struct" (это структура данных внутри PHP-структуры, в которой хранится каждая переменная... ассоциативный массив), который имеет такие ключи, как "значение", "тип", "refcount".
Ответ 2
Вот источник функции array_key_exists для 5.2.17. Вы можете видеть, что даже если ключ имеет значение 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;
}
}
Ответ 3
Не array_key_exists, но удаление ссылки (= NULL) вызывает это. Я прокомментировал это из вашего script, и это результат:
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 );