27

PHP 使用修改时复制系统。

$a = (string) $a;($a is a already string)是否修改和复制任何内容?


特别是,这是我的问题:

参数 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;
        }
    }
}
4

3 回答 3

44

答案是肯定的,它确实复制了字符串。有点……不是真的。好吧,这取决于您对“复制”的定义...

>= 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 已经是一个字符串,则调用使用 which 只是短路。

所以执行复制的代码是(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)

注意这是在做什么。字符串本身不会被复制(存储在->valuezval 的块中)。它只是被引用(指针保持不变,因此字符串值相同,没有副本)。但它正在创建一个新变量(包装值的 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 alpha 1(不包括其他 alpha,因为差异很小,不会产生根本差异)

    Static Var
    220152
    220200
    Dynamic Var
    220152
    220520
    

    所以静态var增加了48字节,动态var增加了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 重复和相关的开销。在最坏的情况下,您正在谈论字符串重复...

于 2013-02-28T16:45:26.340 回答
12

您的代码实际上并没有这样做:

$a = (string)$a;

更像是这样,因为当字符串作为函数参数传递时应用了写时复制语义:

$b = (string)$a;

这两种说法有很大的不同。第一个不会对内存产生任何影响,而第二个会......通常。

以下代码大致完成了您的代码将执行的操作;传递了一些字符串,然后您将其转换并分配给另一个变量。它跟踪内存的增加。

<?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 的答案中看到的那样。

于 2013-02-28T15:47:03.047 回答
7

令人惊讶的是,它确实创建了一个副本:

$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...
于 2013-02-28T15:58:55.197 回答