Por que array_key_exists 1000x mais lento do que isset em arrays referenciados?

Descobri que array_key_exists é superior a 1000x mais lento do que isset na verificação se uma chave estiver definida em uma referência de matriz. Alguém que tenha uma compreensão de como o PHP é implementado explica por que isso é verdade?

EDIT: adicionei outro caso que parece apontar para que seja exigido o excesso de carga nas funções de chamada com uma referência.

Exemplo de referência

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

Saída

 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? 

Estou no PHP 5.3.6.

Exemplo do Codepad .

No trabalho, tenho uma instância VM do PHP que inclui uma extensão PECL chamada VLD. Isso permite que você execute o código PHP da linha de comando e em vez de executá-lo, ele retorna o código aberto gerado.

É shiny em responder a perguntas como esta.

http://pecl.php.net/package/vld

Apenas no caso de você ir esta rota (e se você estiver geralmente curioso sobre como o PHP funciona internamente, acho que você deveria) você definitivamente deveria instalá-lo em uma máquina virtual (isto é, eu não iria instalá-lo em uma máquina i ‘ Estou tentando desenvolver ou implantar para). E este é o comando que você usará para fazê-lo cantar:

 php -d vld.execute=0 -d vld.active=1 -f foo.php 

Olhando para os opcodes irá contar-lhe uma história mais completa, no entanto, tenho um palpite …. A maioria dos built-ins do PHP faz uma cópia de uma matriz / object e atua sobre essa cópia (e não uma cópia em gravação ou uma cópia imediata). O exemplo mais conhecido disso é foreach (). Quando você passa uma matriz para o foreach (), o PHP está realmente fazendo uma cópia dessa matriz e iterando na cópia. É por isso que você verá um benefício de desempenho significativo passando uma matriz como uma referência para foreach assim:

foreach ($ someReallyBigArray como $ k => & $ v)

Mas esse comportamento – que passa em uma referência explícita como essa – é exclusivo para foreach (). Então, ficaria muito surpreso se fizesse um array_key_exists () verificar mais rapidamente.

Ok, de volta ao que eu estava recebendo …

A maioria dos built-in leva uma cópia de uma matriz e atua sobre essa cópia. Eu vou aventurar um palpite completamente desqualificado de que isset () é altamente otimizado e que uma dessas otimizações talvez não faça uma cópia imediata de uma matriz quando ela for passada.

Vou tentar responder a qualquer outra dúvida que você possa ter, mas provavelmente você pode ler muito do seu google para “zval_struct” (que é a estrutura de dados nos componentes internos do PHP que armazena cada variável. É uma estrutura C (pense … uma array associativo) que possui chaves como “valor”, “tipo”, “refcount”.

Aqui está a fonte da function array_key_exists para 5.2.17. Você pode ver que, mesmo que a chave seja nula, o PHP tenta calcular um hash. Embora seja interessante que, se você remover

 // $my_array_ref[$i] = NULL; 

então ele funciona melhor. Deve haver várias pesquisas de hash ocorridas.

 /* {{{ 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; } } 

Não array_key_exists, mas a remoção da referência (= NULL) causa isso. Eu comentei o seu script e este é o resultado:

 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 

Apenas removeu a array_key_exists( $my_array_ref ) da parte array_key_exists( $my_array_ref ) , esta é a parte modificada para referência:

 $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 ); 
Intereting Posts