Smarty(以及其他模板引擎):assign和assign_by_ref

3

这不仅仅是关于Smarty,我猜大多数模板引擎都有变量赋值。这更像是一个理论问题,而不是实际问题。我没有用例。

当你将一个大数组 $a 赋值给另一个变量 $b 时,PHP会发生什么?PHP会复制该数组吗?也许,内部只是创建了一个指针。那么当你稍微改变一下 $a 时会发生什么?$b 不应该被改变,因为没有使用 & 来创建 $b。PHP是否刚刚加倍了内存使用?

更具体地说:当您将一个大数组从您的控制器 ($a) 分配到您的模板引擎 ($tpl->vars['a']) 并在视图中使用 (extract$a) 时会发生什么?PHP的内存是否刚刚增加了三倍?

现在,如果我通过引用分配所有变量会发生什么?我的视图能够将数组更改回控制器(反正我不会回到那里)。如果变量在模板引擎中更改 ($tpl->vars['a']),那也没问题。

通过引用分配所有变量对内存更好吗?对性能更好吗?如果是这样的话:是否存在奇怪的、不希望发生的副作用?

因为人们喜欢代码而不是故事:

// copies
$a = array( ... );
$tpl->assign('a', $a); // creates a copy (?) in $tpl->vars['a']

// pointer / by ref
$a = array( ... );
$tpl->assign_by_ref('a', $a); // creates a pointer in $tpl->vars['a'] because:

function assign_by_ref( $name, &$var ) {
  $this->vars[$name] = $var; // voila pointer?
}

我相信PHP不会介意大数组和复制,但就性能和内存而言,哪个更好呢?
编辑: 对于对象来说,所有这些都无关紧要。对象总是自动通过引用分配的。由于对象很热门,也许这是一个过时的问题,但我非常好奇。
更新: 所以PHP使用写时复制...太棒了。而且对象总是指针。当你做什么时会发生什么:
$a = new BigObject;
$b = $a; // pointer, right?
$b->updateSomethingInternally(); // $b is now changed > what about $a?

这会触发写时复制吗?还是$a和$b仍然相同(就像使用===一样)?
编辑 我能得出结论,通过引用赋值真的不值得只为节省内存吗?PHP本身已经足够聪明了吗?
编辑 关于复制、克隆、按引用传递等的有趣可视化:http://www.phpinsider.com/download/PHP5RefsExplained.pdf

谢谢nikic。忘记了那个代码块。 - Rudie
回应您最后的编辑:是的,这并不值得。在一般情况下,PHP已经足够智能了。此外:您不应该担心这个问题,通常内存对于网站来说不是问题。 - NikiC
3个回答

6

PHP使用一种称为写时复制(Copy On Write)的概念。也就是说,如果你只是执行$a = $b操作,PHP不会将$b的整个值复制到$a中,它只会创建某种指针。(更准确地说,$a$b都将指向同一个zval,并且它的refcount将被增加)

现在,如果$a$b中的任意一个被修改了,那么这个值显然不能再被共享,必须进行复制。

所以,除非你在模板代码中修改数组,否则不会进行复制。

以下是一些进一步的注释:

  • Beware of trying to optimize your code by blindly inserting references. Often they will have an effect contrary to what you expect. Example to explain why:

    $a = SOMETHING_BIG; // $a points to a zval with refcount 1 and is_ref 0
    $b = $a;            // $a and $b both point to a zval with refcount 2 and is_ref 0
    $c =& $a;           // Now we have a problem: $c can't just point to the same zval
                        // anymore, because that zval has is_ref to 0, but we need one
                        // with is_ref 1. So The zval gets copied. You now have $b
                        // pointing to one zval with refcount 1 and is_ref 0 and $a and
                        // $c pointing to another one with refcount 2 and is_ref 1
    

    So the contrary to what you wanted actually happened. Instead of saving memory you are actually allocating additional. It's often hard to judge whether adding a reference will make it better or worse because it's often hard to trace all different variables pointing to one zval (it's often not as easy as it looks, just have a look at the examples of the debug_zval_dump function. So, really, the only safe way to know, whether a reference is good for performance or not, is to actually profile both variants.

  • Objects are, just like everything else, passed by value in PHP. Still you are right that they behave reference-like, because with objects this value that get's passed around is just a pointer to some other data structure. In most cases the distinction between passing by reference and reference-like behavior isn't of importance, but there still is a difference.

这只是一个简短的介绍,有关该主题的更深入分析可以在Sara Golemon的博客文章中找到,标题很刺激:“你正被欺骗”。


哇...这比我预期的要高级一些 =) 但我明白了重点(以及示例中的问题)。我知道PHP是一个聪明的家伙!我正在更新我的原始问题,并提出一个额外的问题 =) - Rudie
@Rudie:我更新了我的答案,包括有关对象的一段内容。但在你的例子中,$a和$b都会改变。这是其中一种可能看到类似引用行为的情况。请参阅webbiedave的答案,以查看证明它不是真正引用的示例。 - NikiC
有趣,我不知道这个! - Pekka

2
正如其他答案中提到的那样,PHP采用写时复制技术。但我确实希望回答您帖子中的这部分内容:
“对象总是自动分配引用。”
这并不完全正确。它们被分配一个标识符,该标识符指向一个对象。
$a = new stdClass();
$b = $a; // $a and $b now share same identifier
$b = 0; // $b no longer contains identifier
var_dump($a); // outputs object

与引用赋值相比,这是一个对比:
$a = new stdClass();
$b =& $a; // $a and $b now share same reference
$b = 0; //
var_dump($a); // outputs int(0)

更新

在你的编辑中,你问道:

$a = new BigObject;
$b = $a; // pointer, right?
$b->updateSomethingInternally(); // $b is now changed > what about $a?

这是否触发了写时复制?或者$a和$b仍然相同(像===一样)?
因为$b现在包含一个标识符,即现在“指向”与$a相同的对象,所以$a也受到影响。从未涉及任何对象的复制。

这是一个非常好的观点!这不是新闻,而是一个非常好的观点。我只是指对象变量中包含的内容。 - Rudie

1

PHP 在传递数组时使用写时复制技术,因此在修改数组之前不会使用额外的内存。很抱歉,没有链接来支持这一说法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接