请解释一下它是如何工作的。为什么将值从变量传递到数组而不是文字,增加了10倍的内存消耗?
PHP 7.1.17
第一个例子:
<?php
ini_set('memory_limit', '1G');
$array = [];
$row = 0;
while ($row < 2000000) {
$array[] = [1];
if ($row % 100000 === 0) {
echo (memory_get_usage(true) / 1000000) . PHP_EOL;
}
$row++;
}内存使用总量~70 memory
第二个例子:
<?php
ini_set('memory_limit', '1G');
$array = [];
$a = 1;
$row = 0;
while ($row < 2000000) {
$array[] = [$a];
if ($row % 100000 === 0) {
echo (memory_get_usage(true) / 1000000) . PHP_EOL;
}
$row++;
}内存总使用量~785 memory
如果结果数组是一维的,那么内存消耗也没有差别。
发布于 2018-06-22 10:46:38
这里的关键是,尽管[1]是一个复杂的值,但它是一个常量--编译器可以很小地知道每次使用它时它都是相同的。
由于当多个变量具有相同的值时,PHP使用“复制就写”系统,因此编译器实际上可以在代码运行之前为数组构造"zval“结构,并且每次新变量或数组值指向它时只增加它的引用计数器。(如果它们中的任何一个稍后被修改,它们将在修改前被“分离”到一个新的zval中,因此在这一点上无论如何都会产生一个额外的副本。)
因此(使用42更突出),如下所示:
$bar = [];
$bar[] = [42];编译为此(用https://3v4l.org生成的VLD输出):
compiled vars: !0 = $bar
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
3 0 E > ASSIGN !0, <array>
4 1 ASSIGN_DIM !0
2 OP_DATA <array>
3 > RETURN 1注意,42甚至没有出现在VLD输出中,而是隐含在第二个<array>中。因此,唯一的内存使用是外部数组存储一长串指针,这些指针恰好指向相同的zval。
另一方面,当使用像[$a]这样的变量时,不能保证所有的值都是相同的。分析代码并推断它们是可能的,因此OpCache可以应用一些优化,但它本身:
$a = 42;
$foo = [];
$foo[] = [$a];汇编成:
compiled vars: !0 = $a, !1 = $foo
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
3 0 E > ASSIGN !0, 42
4 1 ASSIGN !1, <array>
5 2 INIT_ARRAY ~5 !0
3 ASSIGN_DIM !1
4 OP_DATA ~5
5 > RETURN 1注意额外的INIT_ARRAY操作码--这是一个新的zval,它的值为[$a]。这就是所有额外内存的去处--每次迭代都会创建一个具有相同内容的新数组。
这里需要指出的是,如果$a本身是一个复杂的值--数组或对象--它不会在每次迭代中被复制,因为它会有自己的引用计数器。您仍然需要在循环中每次创建一个新数组,但是这些数组都包含一个指向$a的复制上写指针,而不是它的副本。对于整数(在PHP 7中)不会出现这种情况,因为直接存储整数比存储指向存储整数的其他地方的指针要便宜。
还有一个变化值得一看,因为它可能是一种你可以手工进行的优化:
$a = 42;
$b = [$a];
$foo = [];
$foo[] = $a;VLD输出:
compiled vars: !0 = $a, !1 = $b, !2 = $foo
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
3 0 E > ASSIGN !0, 42
4 1 INIT_ARRAY ~4 !0
2 ASSIGN !1, ~4
5 3 ASSIGN !2, <array>
6 4 ASSIGN_DIM !2
5 OP_DATA !0
7 6 > RETURN 1在这里,我们在创建INIT_ARRAY时有一个$b操作码,但是当我们将它添加到$foo时就没有了。ASSIGN_DIM将看到每次重用$b zval并增加其引用计数器是安全的。我还没有进行测试,但我相信这将使您回到与常量[1]情况相同的内存使用情况。
这里使用的最后一种方法是使用转储,它显示了一个值的引用计数。确切的数字总是有点差,因为将变量传递给函数本身会创建一个或多个引用,但是您可以从相对值中得到一个很好的信息:
常数数组:
$foo = [];
for($i=0; $i<100; $i++) {
$foo[] = [42];
}
debug_zval_dump($foo[0]);显示了102的重新计数,因为值是在100份副本之间共享的。
相同但非常量数组:
$a = 42;
$foo = [];
for($i=0; $i<100; $i++) {
$foo[] = [$a];
}
debug_zval_dump($foo[0]);显示2的折算,因为每个值都有它自己的zval。
数组构造一次并显式重用:
$a = 42;
$b = [$a];
$foo = [];
for($i=0; $i<100; $i++) {
$foo[] = $b;
}
debug_zval_dump($foo[0]);显示了102的重新计数,因为值是在100份副本之间共享的。
内部的复杂值(也尝试$a = new stdClass等):
$a = [1,2,3,4,5];
$foo = [];
for($i=0; $i<100; $i++) {
$foo[] = [$a];
}
debug_zval_dump($foo[0]);显示refcount为2,但内部数组的refcount为102:每个外部项都有一个单独的数组,但它们都包含指向创建为$a的zval的指针。
https://stackoverflow.com/questions/50967647
复制相似问题