首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >PHP数组内存消耗

问PHP数组内存消耗
EN

Stack Overflow用户
提问于 2018-06-21 11:37:22
回答 1查看 138关注 0票数 3

请解释一下它是如何工作的。为什么将值从变量传递到数组而不是文字,增加了10倍的内存消耗?

PHP 7.1.17

第一个例子:

代码语言:javascript
复制
<?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

第二个例子:

代码语言:javascript
复制
<?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

如果结果数组是一维的,那么内存消耗也没有差别。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-06-22 10:46:38

这里的关键是,尽管[1]是一个复杂的值,但它是一个常量--编译器可以很小地知道每次使用它时它都是相同的。

由于当多个变量具有相同的值时,PHP使用“复制就写”系统,因此编译器实际上可以在代码运行之前为数组构造"zval“结构,并且每次新变量或数组值指向它时只增加它的引用计数器。(如果它们中的任何一个稍后被修改,它们将在修改前被“分离”到一个新的zval中,因此在这一点上无论如何都会产生一个额外的副本。)

因此(使用42更突出),如下所示:

代码语言:javascript
复制
$bar = [];
$bar[] = [42];

编译为此(用https://3v4l.org生成的VLD输出):

代码语言:javascript
复制
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可以应用一些优化,但它本身:

代码语言:javascript
复制
$a = 42;
$foo = [];
$foo[] = [$a];

汇编成:

代码语言:javascript
复制
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中)不会出现这种情况,因为直接存储整数比存储指向存储整数的其他地方的指针要便宜。

还有一个变化值得一看,因为它可能是一种你可以手工进行的优化:

代码语言:javascript
复制
$a = 42;
$b = [$a];
$foo = [];
$foo[] = $a;

VLD输出:

代码语言:javascript
复制
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]情况相同的内存使用情况。

这里使用的最后一种方法是使用转储,它显示了一个值的引用计数。确切的数字总是有点差,因为将变量传递给函数本身会创建一个或多个引用,但是您可以从相对值中得到一个很好的信息:

常数数组:

代码语言:javascript
复制
$foo = [];
for($i=0; $i<100; $i++) {
    $foo[] = [42];
}
debug_zval_dump($foo[0]);

显示了102的重新计数,因为值是在100份副本之间共享的。

相同但非常量数组:

代码语言:javascript
复制
$a = 42;
$foo = [];
for($i=0; $i<100; $i++) {
    $foo[] = [$a];
}
debug_zval_dump($foo[0]);

显示2的折算,因为每个值都有它自己的zval。

数组构造一次并显式重用:

代码语言:javascript
复制
$a = 42;
$b = [$a];
$foo = [];
for($i=0; $i<100; $i++) {
    $foo[] = $b;
}
debug_zval_dump($foo[0]);

显示了102的重新计数,因为值是在100份副本之间共享的。

内部的复杂值(也尝试$a = new stdClass等):

代码语言:javascript
复制
$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的指针。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50967647

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档