23

キーが配列参照に設定されている場合、チェック時array_key_existsよりも1000倍以上遅いことがわかりました。issetPHPがどのように実装されているかを理解している人は、なぜこれが本当なのか説明していますか?

編集:参照を使用して関数を呼び出すにはオーバーヘッドが必要であることを示していると思われる別のケースを追加しました。

ベンチマークの例

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

職場で、VLD と呼ばれる PECL 拡張機能を含む PHP の VM インスタンスを取得しました。これにより、コマンドラインから PHP コードを実行できます。実行するのではなく、代わりに生成されたオペコードを返します。

このような質問に答えるのが得意です。

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

このルートに行く場合に備えて (そして、PHP が内部でどのように機能するかについて一般的に興味がある場合は、そうするべきだと思います)、間違いなく仮想マシンにインストールする必要があります (つまり、私はそれをマシンにインストールしません。 m で開発またはデプロイしようとしています)。これは、歌わせるために使用するコマンドです。

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() は高度に最適化されており、それらの最適化の 1 つは、渡されたときに配列の即時コピーを実行しないことであるという、完全に限定されていない推測を試みます。

私はあなたが持っているかもしれない他の質問に答えようとしますが、おそらく「zval_struct」(各変数を格納するPHP内部のデータ構造です。これはC構造体です(考えてください.. "value"、"type"、"refcount" などのキーを持つ連想配列)。

于 2011-06-18T12:43:26.507 に答える
3

これは、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, 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 )。これは参照用に変更されたパーツです。

$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 に答える