Как работает управление использованием памяти массивов PHP?

Я пытаюсь понять, как PHP загружает массивы в память и когда передача массива потребляет память.

Итак, у меня получилось немного кода: обратите внимание, что входной массив менее важен в этом примере:

<?php

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

Это потребляет ровно 250 кбайт памяти, это означает, что размер массива составляет примерно 250 кБ, примерно.

Итак, я запустил следующий код:

<?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 КБ ОЗУ.

Кто-нибудь может объяснить, что здесь происходит?

Чтобы быть ясным, все эти индексы (0-5 и id) уже существуют в исходном массиве, Im просто модифицирует значение. Исходным значением является некоторое целое число.

ИЗМЕНИТЬ

Просто, чтобы очистить участие $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
DIFF TO DECLARATION: 4296 kB

Итак, хотя объявление было 4608, и массив был передан и изменен 4 раза, он все же только меньше, чем удвоил объем памяти.

РЕДАКТИРОВАТЬ № 3

Я выполнял изменения памяти после каждого выделения:

ДЕКЛАРАЦИЯ: 5144 kB
выделение A0 добавлено: 144 kB
выделение А1 добавлено: 1768 kB
выделение A2 добавлено: 1768 kB
выделение A3 добавлено: 1768 kB
ОКОНЧАТЕЛЬНЫЙ: 10744 kB
DIFF TO DECLARATION: 5600 kB

Каждая следующая операция после первого стоит точно так же, что, похоже, указывает на точный размер, который копируется. Это похоже на поддержку ответа Остина. Единственное, что сейчас не складывается, это размер, который был выделен. Но это другой вопрос.

Кажется, что Остин на балу, я соглашусь, если не будет ответа.

Ответы

Ответ 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