我正在试图弄清楚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必须复制整个数组。< / p>
想象一下,当上面的代码消耗恰好500 kB的RAM时,我感到很惊讶。
有谁可以解释这里发生了什么?
为了清楚起见,所有这些索引(0-5和id
)已经存在于原始数组中,我只是修改了值。原始值是一些整数。
修改
只是为了清除$ this-&gt; 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
DIFF to DECLARATION:4296 kB
因此即使声明是4608并且数组已经传递并更改了4次,它仍然只是内存占用量的两倍。
编辑#3
我在每次分配后都运行了内存更改:
声明:5144 kB
分配A0加:144 kB
分配A1添加:1768 kB
分配A2添加:1768 kB
分配A3添加:1768 kB
最终:10744 kB
DIFF to DECLARATION:5600 kB
每个后续操作后的第一个成本完全相同,这似乎表明正在复制完全相同的大小。这似乎支持奥斯汀的答案,现在唯一没有加起来的是分配的大小,但这是一个不同的问题。
看起来像奥斯汀的球,如果没有其他答案,我会接受它。
答案 0 :(得分:4)
以下是我的想法:
正如你所说,PHP数组是写入时的复制,但是多维数组的每个级别都是在写入时单独复制的。 PHP非常聪明地重用多维数组的部分而不仅仅是整个部分。 (这类似于支持快照的一些文件系统,如ZFS。)
示例:说我们有这个数组
$x = array('foo' => array(1, 2, 3), 'bar' => array(4, 5, 6));
这不是作为单个块存储在内存中,而是作为单独的块标记为A
,B
,C
和$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
请注意,B
和B2
相同。没有必要保持两次相同的东西,所以实际发生的是:
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