web-dev-qa-db-ja.com

(文字列) '文字列をハードコピー'しますか?

PHPはコピーオンモディフィケーションシステムを使用しています。

$a = (string) $a;($ aはすでに文字列です)は何かを変更してコピーしますか?


特に、これは私の問題です。

パラメータ1はmixed /非文字列を渡して文字列に変換できるようにしたい。
しかし、これらの文字列が非常に大きい場合があります。したがって、すでに文字列であるパラメータのコピーを省略したいと思います。

バージョンFooを使用できますか、それともバージョンBarを使用する必要がありますか?

class Foo {
    private $_foo;
    public function __construct($foo) {
        $this->_foo = (string) $foo;
    }
}

class Bar {
    private $_bar;
    public function __construct($bar) {
        if (is_string($bar)) {
            $this->_bar = $bar;
        } else {
            $this->_bar = (string) $bar;
        }
    }
}
27
mzimmer

答えは、はい、文字列をコピーするということです。並べ替え...そうではありません。まあ、それはあなたの「コピー」の定義に依存します...

> = 5.4

何が起こっているかを確認するために、ソースを見てみましょう。エグゼキュータは変数キャストを処理します ここでは5.5

_    zend_make_printable_zval(expr, &var_copy, &use_copy);
    if (use_copy) {
        ZVAL_COPY_VALUE(result, &var_copy);
        // if optimized out
    } else {
        ZVAL_COPY_VALUE(result, expr);
        // if optimized out
        zendi_zval_copy_ctor(*result);
    }
_

ご覧のとおり、呼び出しは zend_make_printable_zval() を使用します。これは、zvalがすでに文字列である場合に短絡します。

したがって、コピーを実行するために実行されるコードは(elseブランチ)です。

_ZVAL_COPY_VALUE(result, expr);
_

それでは、 _ZVAL_COPY_VALUE_ の定義を見てみましょう:

_#define ZVAL_COPY_VALUE(z, v)                   \
    do {                                        \
        (z)->value = (v)->value;                \
        Z_TYPE_P(z) = Z_TYPE_P(v);              \
    } while (0)
_

それが何をしているのかに注意してください。文字列自体は[〜#〜] not [〜#〜]コピーされます(これはzvalの_->value_ブロックに格納されます)。参照されているだけです(ポインタは同じままなので、文字列値は同じで、コピーはありません)。ただし、新しい変数(値をラップするzval部分)を作成しています。

ここで、 _zendi_zval_copy_ctor_ 呼び出しに入ります。これは内部的にそれ自体でいくつかの興味深いことを行います。注意:

_case IS_STRING:
    CHECK_ZVAL_STRING_REL(zvalue);
    if (!IS_INTERNED(zvalue->value.str.val)) {
        zvalue->value.str.val = (char *) estrndup_rel(zvalue->value.str.val, zvalue->value.str.len);
    }
    break;
_

基本的に、これは、インターンされた文字列の場合、コピーされないことを意味します。しかし、そうでない場合は、がコピーされます...では、インターンされた文字列とは何ですか?それはどういう意味ですか?

<= 5.3

5.3では、インターンされた文字列は存在しませんでした。したがって、文字列は常にコピーされます。それが本当に唯一の違いです...

ベンチマーク時間:

まあ、このような場合:

_$a = "foo";
$b = (string) $a;
_

5.4では文字列のコピーは発生しませんが、5.3ではコピーが発生します。

しかし、このような場合:

_$a = str_repeat("a", 10);
$b = (string) $a;
_

コピーはすべてのバージョンで発生します。これは、PHPではすべての文字列がインターンされているわけではないためです...

ベンチマークで試してみましょう: http://3v4l.org/HEelW

_$a = "foobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisoutfoobarbizbazbuztestingthisout";
$b = str_repeat("a", 300);

echo "Static Var\n";
testCopy($a);
echo "Dynamic Var\n";
testCopy($b);

function testCopy($var) {
    echo memory_get_usage() . "\n";
    $var = (string) $var;
    echo memory_get_usage() . "\n";
}
_

結果:

  • 5.4-5.5アルファ1(違いは根本的な違いをもたらさないほど小さいため、他のアルファは含まれません)

    _Static Var
    220152
    220200
    Dynamic Var
    220152
    220520
    _

    したがって、静的変数は48バイト増加し、動的変数は368バイト増加しました。

  • 5.3.11から5.3.22:

    _Static Var
    624472
    625408
    Dynamic Var
    624472
    624840
    _

    静的変数は936バイト増加し、動的変数は368バイト増加しました。

したがって、5.3では、静的変数と動的変数の両方がコピーされていることに注意してください。したがって、文字列は常に複製されます。

しかし、静的文字列を使用する5.4では、zval構造のみがコピーされました。インターンされた文字列自体は同じままで、コピーされないことを意味します。

もう一つのこと

注意すべきもう一つのことは、上記のすべてが議論の余地があるということです。変数をパラメーターとして関数に渡します。次に、関数内にキャストします。したがって、コピーオンライトは回線によってトリガーされます。したがって、これを実行すると常に(99.9%の場合)変数コピーがトリガーされます。したがって、せいぜい(インターン文字列)、zvalの重複とそれに関連するオーバーヘッドについて話していることになります。最悪の場合、あなたは文字列の重複について話している...

43
ircmaxell

あなたのコードは実際にはしません:

$a = (string)$a;

文字列が関数の引数として渡されるときにコピーオンライトのセマンティクスが適用されるため、これはより似ています。

$b = (string)$a;

これらの2つのステートメントの間にはかなり大きな違いがあります。前者はメモリに影響を与えませんが、後者は通常...です。

次のコードは、おおよそあなたのコードがすることをします。いくつかの文字列が渡され、それをキャストして別の変数に割り当てます。メモリの増加を追跡します。

<?php

$x = 0;
$y = 0;

$x = memory_get_usage();

$s = str_repeat('c', 1200);

$y = memory_get_usage();

echo $y - $x, PHP_EOL;

$s1 = (string)$s;

$x = memory_get_usage();

echo $x - $y, PHP_EOL;

結果(5.4.9)

1360
1360

結果(5.3.19)

1368
1368

割り当ては基本的に文字列値全体をコピーします。

文字列リテラルの使用

文字列リテラルを使用する場合、動作はバージョンによって異なります。

<?php

$x = 0;
$y = 0;

$x = memory_get_usage();

$s = 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc';

$y = memory_get_usage();

echo $y - $x, PHP_EOL;

$s1 = (string)$s;

$x = memory_get_usage();

echo $x - $y, PHP_EOL;

結果(5.4.9)

152
136

結果(5.3.19)

1328
1328

その理由は、 ircmaxellの回答 から読み取ることができるように、文字列リテラルがエンジンによって異なる方法で処理されるためです。

12
Ja͢ck

驚いたことに、それはコピーを作成します:

$string = "TestMe";
debug_zval_dump($string);

$string2 = $string;
debug_zval_dump($string);

$string3 = $string;
debug_zval_dump($string);

$string4 = (string) $string;
debug_zval_dump($string);

$string5 = (string) $string;
debug_zval_dump($string);

出力:

string(6) "TestMe" refcount(2)
string(6) "TestMe" refcount(3)
string(6) "TestMe" refcount(4)
string(6) "TestMe" refcount(4)
string(6) "TestMe" refcount(4)

別の証拠:

echo memory_get_usage(), PHP_EOL;

$s = str_repeat('c', 100000);
echo memory_get_usage(), PHP_EOL;

$s1 = $s;
echo memory_get_usage(), PHP_EOL;

$s2 = (string) $s;
echo memory_get_usage(), PHP_EOL;

出力:

627496
727664
727760  # small increase, new allocated object, but no string copy
827928  # oops, we copied the string...
7
Karoly Horvath