PHP 空值和写入时复制


PHP null and copy-on-write

>假设我想有两个变量,并且它们都等于null。(更现实地说,我正在考虑一个包含大量 null s 的数组,但"两个变量"场景足以满足这个问题。显然,我可以通过多种方式做到这一点。我可以这样做(方法 1):

$a = null;
$b = $a;

根据我的理解,这样做的结果是符号表中有两个条目指向一个 zval:'a''b' 。但也可以这样做(方法 2):

$a = null;
$b = null;

人们会天真地认为这应该导致两个不同的 zval,每个 zval 都指向符号表中的一个条目。

是否由此

得出,如果你想拥有一个大数组,并且数组的许多元素将被null,那么创建一个值为 null$master_null 变量更有效(就 zval/内存使用而言),然后通过使用 $master_null 赋值来编写数组的null元素?

请考虑以下脚本:

$arr = array();
for ($i = 0; $i < 100000; $i++) $arr[] = null;
echo memory_get_usage() . "'n";

在我的机器上输出:21687696,即 21 MB 的已用内存。另一方面,使用它:

$master_null = null;
$arr = array();
for ($i = 0; $i < 100000; $i++) $arr[] = $master_null;
echo memory_get_usage() . "'n";

输出:13686832,即 13 MB。根据此信息,您可以假设就内存使用而言,实际上最好确实使用"master null"变量。但是,您仍然需要拥有数组中的所有项目,并且 HashTable 中的每个条目(数组的内部表示)也会占用一些内存。

如果您想更深入地挖掘 zvals 和引用,我建议使用函数 debug_zval_dump .使用它,您可以看到哪些变量共享相同的 zval:

$a = $b = $c = $d = "abc";
debug_zval_dump($a);
$x = $y = $z = $w = null; 
debug_zval_dump($x);
$q = null;
debug_zval_dump($q);

其中输出:

string(3) "abc" refcount(5)
NULL refcount(5)
NULL refcount(2)

这意味着尽管变量 $x 和 $q 都是 NULL,但它们不是同一个 zval。但是$x和$y共享相同的 zval,因为它们是相互分配的。我相信您知道函数debug_zval_dump,但如果没有,请确保您仔细阅读了 refcount 解释 http://php.net/manual/en/function.debug-zval-dump.php。

同样在我的文章末尾,我想说这些信息可能对更好地了解 PHP 内部很有用,我认为进行任何优化都是毫无用处的。主要是因为比这种微优化有更好的地方开始优化脚本。此外,虽然这不是规范的一部分,但PHP作者可能会在未来改变这种行为(例如,所有NULL变量在未来的版本中可以共享相同的zval)。

据我了解,PHP zval 容器具有引用计数逻辑。因此,我的印象是,如果您使用引用即 &$master_null 来初始化所有 NULL 值,我认为这可以节省您的空间,即数组的所有 NULL 项目都指向对 zval 容器的相同引用。

下面是一个示例:

# php -r '$var1 = NULL; $var2 = $var1; $var3 = $var1; debug_zval_dump(&$var1); debug_zval_dump(&$var2); debug_zval_dump(&$var3);'
&NULL refcount(2)
&NULL refcount(2)
&NULL refcount(2)

您可以在此处阅读有关PHP的引用计数基础的更多信息:

从此链接中值得阅读的是:

PHP is smart enough not to copy the actual variable container
when it is not necessary. Variable containers get destroyed 
when the "refcount" reaches zero. The "refcount" gets decreased by 
one when any symbol linked to the variable container leaves the 
scope (e.g. when the function ends) or when unset() is called on a symbol.

因此,每次使用 &$master_null 时,它的"refcount"都会增加,当"refcount"达到零时,变量容器将从内存中删除。


从上面的注释示例中,这里是内存使用情况:

# php -r '$arr = array(); for ($i = 0; $i < 100000; $i++) $arr[] = null; echo memory_get_usage() . "'n";'
11248372
# php -r '$master_null = null; $arr = array(); for ($i = 0; $i < 100000; $i++) $arr[] = &$master_null; echo memory_get_usage() . "'n";'
6848488
# php -r '$master_null = null; $arr = array(); for ($i = 0; $i < 100000; $i++) $arr[] = $master_null; echo memory_get_usage() . "'n";'
6848468

不,所有要实现的只是您将拥有一个名为 $master_null 的额外变量。它们都指向 null。让它们各自指向$master_null是一回事。