【问题标题】:How does PHP’s array memory usage management work?PHP 的数组内存使用管理是如何工作的?
【发布时间】:2015-01-11 18:00:32
【问题描述】:

我试图弄清楚 PHP 是如何将数组加载到内存中的,以及传递数组何时会消耗内存。

所以我运行了这段代码:注意输入数组在这个例子中不太重要:

<?php

echo $this->getMemoryUsage();
$arr = $query->result_array(); // array of arrays from codeigniter
echo $this->getMemoryUsage();

这正好消耗 250 kB 的内存,这意味着数组的大小大约为 250 kB,大约

所以我运行了以下代码:

<?php

echo $this->getMemoryUsage();
$arr = $query->result_array(); // array of arrays from codeigniter

$arr[0]['id'] = 'changing this value';

$foo = $arr;
$foo[2]['id'] = 'changing this value again';

$bar = $foo;
$bar[4]['id'] = 'changing this value again and again';

$far = $bar;
$far[5]['id'] = 'changing this value again and again and again';

echo $this->getMemoryUsage();

根据我阅读和被告知的内容,PHP 实际上并没有复制数组,它只引用原始数组,但是一旦进行了更改,PHP 就必须复制整个数组。

想象一下,当上面的代码恰好消耗 500 kB 的 RAM 时,我会感到惊讶。

谁能解释这里发生了什么?

为了清楚起见,所有这些索引(0-5 和id)已经存在于原始数组中,我只是在修改值。原始值是某个整数。

编辑

只是为了清除 $this->result() 的参与;这是我进行的另一项测试:

    echo $this->getMemoryUsage();
    $arr = $query->result_array(); // array of arrays from codeigniter
//$arr[0]['id'] = 'changing this value';

    $foo = $arr;
    $foo[2]['id'] = 'changing this value again';

    //$bar = $foo;
    //$bar[4]['id'] = 'changing this value again and again';
    //
    //$far = $bar;
    //$far[4]['id'] = 'changing this value again and again and again';

    echo $this->getMemoryUsage();

这次的输出正好是 250 kB - 就像原来的试用版一样,没有任何变化

编辑#2

根据要求,我在设置中运行了此处的代码,以确保结果一致: http://pastebin.com/cYNg4cg7

这些是结果:

声明:4608 kB
最终:8904 KB
与声明的差异:4296 kB

因此,即使声明为 4608 并且数组被传递和更改了 4 次,它仍然只增加了不到一倍的内存占用。

编辑#3

我已经在每次分配后运行了内存更改:

声明:5144 kB
分配 A0 添加:144 kB
分配 A1 添加:1768 kB
分配 A2 添加:1768 kB
分配 A3 添加:1768 kB
最终:10744 KB
声明的差异:5600 kB

第一次之后的每个后续操作的成本完全相同,这似乎表明正在复制完全相同的大小。这似乎支持奥斯汀的回答,现在唯一没有加起来的是分配的大小,但这是一个不同的问题。

看来奥斯汀很喜欢,如果没有其他答案,我会接受。

【问题讨论】:

  • 非常棘手的问题,你可能对我前几天读到的以下文章感兴趣:nikic.github.io/2011/12/12/…
  • 我几周前读过那篇文章,说实话很吸引人,但没有解释复制的具体工作原理。
  • 我知道,只是觉得您可能会喜欢它。我不能回答你的问题,也不能给你一个可以回答你问题的链接。相反,我为你的问题加了星标,这样我就可以关注它,如果没有发布答案,我会给它一个赏金,因为我也很想知道这一点。 :)
  • 我很想知道更多关于幕后发生的事情。在相关说明中,如果您担心“数组”(真正的哈希表)的内存使用情况,您可以使用 SplFixedArray 来获取“真实”数组:php.net/manual/en/class.splfixedarray.php
  • 数组是否真的消耗了 250Kb,或者这也可能是调用的开销(由 result_array() 方法分配的东西,并且(尚未)清理)。可以肯定的是,我会制作一个数组的新副本并测量该副本之前和之后的差异,尽管这也不是完全防水的。

标签: php memory


【解决方案1】:

以下是我的想法:

如您所说,PHP 数组是在写入时复制的,但是多维数组的每一级都是在写入时单独复制的。 PHP 非常聪明地重用多维数组的一部分,而不仅仅是整个数组。 (这类似于一些支持快照的文件系统,如 ZFS。)

例子:假设我们有这个数组

$x = array('foo' => array(1, 2, 3), 'bar' => array(4, 5, 6));

这不是作为单个块存储在内存中,而是作为单独的块存储在此处,标记为 ABC$x

array(1, 2, 3) //A
array(4, 5, 6) //B
array('foo' => {pointer to A}, 'bar' => {pointer to B}) //C
{pointer to C} //$x

现在让我们复制$x

$y = $x;

这使用很少的额外内存,因为它所要做的就是创建另一个指向C的指针:

array(1, 2, 3) //A
array(4, 5, 6) //B
array('foo' => {pointer to A}, 'bar' => {pointer to B}) //C
{pointer to C} //$x
{pointer to C} //$y

现在让我们更改$y

$y['foo'][0] = 10;

以下是不会发生的事情:

array(1, 2, 3) //A
array(10, 2, 3) //A2
array(4, 5, 6) //B
array(4, 5, 6) //B2
array('foo' => {pointer to A}, 'bar' => {pointer to B}) //C
array('foo' => {pointer to A2}, 'bar' => {pointer to B2}) //C2
{pointer to C} //$x
{pointer to C2} //$y

注意BB2 是相同的。没有必要将相同的东西保留两次,所以实际发生的情况是这样的:

array(1, 2, 3) //A
array(10, 2, 3) //A2
array(4, 5, 6) //B
array('foo' => {pointer to A}, 'bar' => {pointer to B}) //C
array('foo' => {pointer to A2}, 'bar' => {pointer to B}) //C2
{pointer to C} //$x
{pointer to C2} //$y

在这个简单的例子中,收益非常小,但想象一下'bar' 数组包含数千个数字,而不是三个数字。您最终会节省大量内存。

将此与您的原始代码相关联,尝试不仅在开始和结束时打印内存使用情况,而且在每次新数组分配之后打印。您会看到,在每一步之后,内存使用量仅增加了原始数组占用量的一小部分。这是因为只有部分数组被复制,而不是全部。具体来说,第一级数组和你更改的特定子数组会被复制,但其他子数组不会被复制。

由于您的代码的特定设置和您制作的数组副本的数量,最终使用的内存量是起始量的两倍似乎是巧合。

(实际上,PHP 可以做得比我在这里描述的更好(它可能只保留一份 'foo''bar' 等),但在大多数情况下,它归结为相同的类型诡计。)

如果您想对此进行更生动的演示,请执行以下操作:

$base = memory_get_usage();
$x = array('small' => array('this is small'), 'big' => array());
for ($i = 0; $i < 1000000; $i++) {
    $x['big'][] = $i;
}
echo (memory_get_usage() - $base).PHP_EOL; //a lot of memory
$y = $x;
$y['small'][0] = 'now a bit bigger';
echo (memory_get_usage() - $base).PHP_EOL; //a bit more memory
$z = $x;
$z['big'][0] = 2;
echo (memory_get_usage() - $base).PHP_EOL; //a LOT more memory

【讨论】:

  • 嘿,我在想这些方面的东西,但是它并没有加起来,使用另一个测试用例已经重现了在 4 次相同操作后内存分配翻倍(请参阅问题中的最后一个编辑) .对相同大小的数组执行 4 个相同的操作应该具有线性效应。如果我们做 4 次完全相同的动作,单次应该花费 1/4 的成本。请注意,在测试中我不断更改不同的索引,如果我们复制一个,我们就复制所有。我会尽快用新的内存值编辑我的问题。
  • @Patrick 第一个操作不会花费太多,因为您不必保留旧值,因为没有变量使用它。第二到第四个动作必须制作副本,因为原件仍在使用中。每次分配后打印内存使用情况以查看此内容。
  • 经过更多测试,您的答案确实正确,棒极了! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 2014-09-24
  • 2012-01-30
  • 2011-11-06
相关资源
最近更新 更多