Ответ 1
Здесь я думаю, что происходит:
Массивы 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