(字符串)'硬拷贝'字符串?

mzi*_*mer 27 php string copy-on-write

PHP使用修改后复制系统.

请问$a = (string) $a;($ a是已经串)修改和复制任何东西?


特别是,这是我的问题:

参数1是mixed/我想允许传递非字符串并将它们转换为字符串.
但有时这些字符串非常大.所以我想省略复制param,这已经是一个字符串了.

我可以使用版本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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

irc*_*ell 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);
    }
Run Code Online (Sandbox Code Playgroud)

如您所见,zend_make_printable_zval()如果zval已经是字符串,则调用仅使用短路.

因此,执行复制的代码是(else分支):

ZVAL_COPY_VALUE(result, expr);
Run Code Online (Sandbox Code Playgroud)

现在,让我们来看看定义ZVAL_COPY_VALUE:

#define ZVAL_COPY_VALUE(z, v)                   \
    do {                                        \
        (z)->value = (v)->value;                \
        Z_TYPE_P(z) = Z_TYPE_P(v);              \
    } while (0)
Run Code Online (Sandbox Code Playgroud)

注意那是在做什么.字符串本身不会被复制(存储在->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;
Run Code Online (Sandbox Code Playgroud)

基本上,这意味着如果它是一个实习字符串,它将不会被复制.但如果不是,它将被复制 ......那么什么是实习字符串,这是什么意思?

<= 5.3

在5.3中,没有实习字符串.所以字符串总是被复制.这真的是唯一的区别......

基准时间:

那么,在这样的情况下:

$a = "foo";
$b = (string) $a;
Run Code Online (Sandbox Code Playgroud)

在5.4中不会发生字符串的副本,但在5.3中将发生副本.

但在这种情况下:

$a = str_repeat("a", 10);
$b = (string) $a;
Run Code Online (Sandbox Code Playgroud)

所有版本都出现副本.那是因为在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";
}
Run Code Online (Sandbox Code Playgroud)

结果:

  • 5.4 - 5.5 alpha 1(不包括其他alphas,因为差异很小,不能产生根本区别)

    Static Var
    220152
    220200
    Dynamic Var
    220152
    220520
    
    Run Code Online (Sandbox Code Playgroud)

    因此静态var增加了48个字节,动态var增加了368个字节.

  • 5.3.11至5.3.22:

    Static Var
    624472
    625408
    Dynamic Var
    624472
    624840
    
    Run Code Online (Sandbox Code Playgroud)

    静态var增加了936个字节,而动态var增加了368个字节.

请注意,在5.3中,静态变量和动态变量都被复制了.所以字符串总是重复的.

但是在静态字符串的5.4中,只复制了zval结构.这意味着被拦截的字符串本身保持不变并且不被复制......

另一件事

另外需要注意的是,以上所有都是没有实际意义的.您将变量作为参数传递给函数.然后你在函数内部进行投射.因此,您的线路将触发写入时复制.所以一直运行(好吧,在99.9%的情况下)会触发变量副本.所以最好(实习字符串)你谈论的是zval重复和相关的开销.在最坏的情况下,你在谈论字符串重复......

  • 我喜欢知道内部如何以及为什么有效; 不只是它确实如此.+1 (4认同)

Ja͢*_*͢ck 12

你的代码实际上并没有:

$a = (string)$a;
Run Code Online (Sandbox Code Playgroud)

它更像是这样,因为当字符串作为函数参数传递时,应用copy-on-write语义:

$b = (string)$a;
Run Code Online (Sandbox Code Playgroud)

这两个陈述之间有很大的不同.第一个不会对内存造成影响,而第二个通常会......

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

<?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;
Run Code Online (Sandbox Code Playgroud)

结果(5.4.9):

1360
1360
Run Code Online (Sandbox Code Playgroud)

结果(5.3.19):

1368
1368
Run Code Online (Sandbox Code Playgroud)

赋值基本上复制整个字符串值.

使用字符串文字

使用字符串文字时,行为取决于版本:

<?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;
Run Code Online (Sandbox Code Playgroud)

结果(5.4.9):

152
136
Run Code Online (Sandbox Code Playgroud)

结果(5.3.19):

1328
1328
Run Code Online (Sandbox Code Playgroud)

原因是引擎对字符串文字的处理方式不同,您可以从ircmaxell的答案中读取.

  • @KarolyHorvath嗯,引用计数与内存消耗不同; 我不确定refcount是否与此相关. (2认同)
  • @MichelZimmer:但是如果你将`$ a`传递给函数并在函数内部执行此操作,`$ a =(string)$ a`与`$ b =(string)$ a`相同,因为复制 - 写入语义.所以是的,你关注第二个... (2认同)

Kar*_*ath 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);
Run Code Online (Sandbox Code Playgroud)

输出:

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)
Run Code Online (Sandbox Code Playgroud)

另一个证据:

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;
Run Code Online (Sandbox Code Playgroud)

输出:

627496
727664
727760  # small increase, new allocated object, but no string copy
827928  # oops, we copied the string...
Run Code Online (Sandbox Code Playgroud)