PHP uses a copy-on-modification system.
$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;
}
}
}
答案 0 :(得分:43)
答案是肯定的,它会复制字符串。排序...不是真的。那么,这取决于你对“复制”的定义......
要了解发生了什么,让我们来看看来源。执行程序处理变量强制转换in 5.5 here。
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);
现在,让我们看一下the definition of 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中,实习字符串不存在。所以字符串总是被复制。这真的是唯一的区别......
嗯,在这样的情况下:
$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(不包括其他alphas,因为差异很小,不能产生根本区别)
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
静态var增加了936个字节,而动态var增加了368个字节。
请注意,在5.3中,静态变量和动态变量都被复制了。所以字符串总是重复的。
但是在静态字符串的5.4中,只复制了zval结构。这意味着被拦截的字符串本身保持不变并且不被复制......
另外需要注意的是,上述所有内容都没有实际意义。您将变量作为参数传递给函数。然后你在函数内部进行投射。因此,您的线路将触发写入时复制。因此,总是(在99.9%的情况下)运行会触发变量副本。所以最好(实习字符串)你谈论的是zval重复和相关的开销。在最坏的情况下,你在谈论字符串重复...
答案 1 :(得分: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;
1360
1360
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;
152
136
1328
1328
原因是引擎对字符串文字的处理方式不同,您可以从ircmaxell's answer读取。
答案 2 :(得分: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...