Ответ
В PHP массивы (тип array) — это упорядоченные хеш-таблицы, что определяет специфику работы с памятью:
-
Динамическое выделение с запасом (capacity): При добавлении элементов PHP выделяет память не поштучно, а блоками. Если массив превышает текущую вместимость, происходит дорогостоящая операция rehash — выделение нового, большего блока памяти и перераспределение всех существующих элементов.
$array = []; // Выделена начальная вместимость (например, на 8 элементов) for ($i = 0; $i < 100; $i++) { $array[$i] = $i; // PHP несколько раз увеличит вместимость (8 -> 16 -> 32 -> 64 -> 128) } -
Высокий overhead на элемент: Каждый элемент массива (
Bucket) хранит не только значение и ключ (хэш), но и ссылки для поддержки двусвязного списка (для сохранения порядка обхода). В PHP 8.x overhead составляет около 56 байт на элемент (плюс память на само значение и ключ). -
Память освобождается не сразу при
unset(): При удалении элемента PHP лишь помечаетBucketкак неиспользуемый (IS_UNDEF). Физическое освобождение памяти и сокращение размера массива может произойти только при следующем рехеше или при вызовеarray_values()(который создает новый массив).$arr = range(1, 1000); // ~56KB + данные unset($arr[500]); // Память не вернется системе, элемент помечен UNDEF $arr = array_values($arr); // Создается новый массив, старый удаляется GC -
Оптимизации:
SplFixedArray: Для массивов с целочисленными ключами, известными заранее, использует C-подобный массив, почти не имеющий overhead.- Предварительное выделение: Если известен примерный размер, можно задать его через
$array = new SplFixedArray(10000);или использоватьarray_fill(0, $size, null)для обычного массива, чтобы минимизировать рехеши. - Явная очистка: Для больших массивов лучший способ освободить память — присвоить переменной
nullили вызватьunset()на всей переменной.
Ответ 18+ 🔞
А, PHP-массивы, ёпта! Ну это же классика, ебать мои старые костыли. Смотри, как тут всё устроено, а то многие думают, что array — это просто линейный список, как в учебнике. Хуй там плавал!
Представь себе не аккуратную полочку, а здоровенную хеш-таблицу, куда всё скидывается в порядке добавления. И вот из-за этой хитрой жопы начинаются все танцы с бубном вокруг памяти.
Во-первых, они жадные до памяти, овердохуища. Каждый раз, когда ты добавляешь элемент, PHP не бежит в систему за каждым байтиком. Нет, он заранее резервирует себе местечко с запасом (это capacity). А когда этот запас кончается — вот тут начинается пиздец под названием rehash. Система выделяет новый, больший кусок памяти и начинает туда перетаскивать ВСЁ, что уже есть. Представь, ты переезжаешь на новую квартиру каждый раз, когда купил ещё одну пару носков.
$array = []; // Тут тебе выделили условно место под 8 элементов, типа "на первое время"
for ($i = 0; $i < 100; $i++) {
$array[$i] = $i; // А ты начинаешь пихать сотню. Он раз — расширился до 16, потом до 32, 64, 128... Вечный переезд!
}
Во-вторых, сам элемент массива — это не просто коробочка. Это целая структура Bucket, которая тащит за собой кучу служебной фигни: и значение, и ключ, и хэш, и ещё ссылки вперёд-назад, чтобы сохранять тот самый порядок, в котором ты всё засунул. В итоге на каждый твой элемент навешивается около 56 байт служебного веса, ещё до того, как ты туда положил хоть что-то! Сам от себя охуеешь, когда посчитаешь.
В-третьих, освобождение — это отдельный цирк. Думаешь, сделал unset() и память тут же свободна? Да похуй! PHP просто помечает эту ячейку как "тут пусто, но место моё". Оно так и болтается мёртвым грузом. Чтобы по-честному всё сжать и вернуть память, нужно либо дождаться следующего рехеша (когда массив снова вырастет), либо вручную вызвать array_values(), который создаст НОВЫЙ массив из старых значений, а старый выкинет сборщиком мусора.
$arr = range(1, 1000); // Около 56 килобайт служебки + сами данные. Нехило так.
unset($arr[500]); // Память системе не вернётся! Просто висит табличка "не занято".
$arr = array_values($arr); // Вот теперь создаётся свежий массив, а старый — в утиль.
Ну и что делать-то, спросишь? Есть парочка приёмов для особо одарённых:
SplFixedArray: Если у тебя ключи — просто числа по порядку и размер известен, бери эту штуку. Там внутри почти чистый C-массив, без всей этой ебанины со связными списками. Разница в памяти и скорости — просто небо и земля.- Заранее намекни размер: Если знаешь, что будет много, либо создай через
new SplFixedArray(10000), либо хотя быarray_fill(0, $size, null). Так ты с первого раза выделишь нужный объём и избежишь этих ебучих переездов-рехешей. - Чистись по-крупному: Чтобы точно освободить память от здоровенного массива, не ковыряйся в нём по элементам. Просто скажи
$bigArray = null;илиunset($bigArray);. Всё, приехали. Память вернётся системе, когда сборщик соизволит.
Вот такая, блядь, магия. С виду простой массив, а под капотом — целый ёперный театр с переездами, служебными квартирантами и мёртвыми душами, которые место занимают.