PHP 的数组内存使用管理是如何工作的?

How does PHP’s array memory usage management work?

我想弄清楚 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

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

看来Austin 很在行,如果没有其他答案我就接受了。

这是我认为正在发生的事情:

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