Назови особенности выделения памяти для массивов в PHP.

Ответ

В PHP массивы (тип array) — это упорядоченные хеш-таблицы, что определяет специфику работы с памятью:

  1. Динамическое выделение с запасом (capacity): При добавлении элементов PHP выделяет память не поштучно, а блоками. Если массив превышает текущую вместимость, происходит дорогостоящая операция rehash — выделение нового, большего блока памяти и перераспределение всех существующих элементов.

    $array = []; // Выделена начальная вместимость (например, на 8 элементов)
    for ($i = 0; $i < 100; $i++) {
        $array[$i] = $i; // PHP несколько раз увеличит вместимость (8 -> 16 -> 32 -> 64 -> 128)
    }
  2. Высокий overhead на элемент: Каждый элемент массива (Bucket) хранит не только значение и ключ (хэш), но и ссылки для поддержки двусвязного списка (для сохранения порядка обхода). В PHP 8.x overhead составляет около 56 байт на элемент (плюс память на само значение и ключ).

  3. Память освобождается не сразу при unset(): При удалении элемента PHP лишь помечает Bucket как неиспользуемый (IS_UNDEF). Физическое освобождение памяти и сокращение размера массива может произойти только при следующем рехеше или при вызове array_values() (который создает новый массив).

    $arr = range(1, 1000); // ~56KB + данные
    unset($arr[500]);      // Память не вернется системе, элемент помечен UNDEF
    $arr = array_values($arr); // Создается новый массив, старый удаляется GC
  4. Оптимизации:

    • 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);. Всё, приехали. Память вернётся системе, когда сборщик соизволит.

Вот такая, блядь, магия. С виду простой массив, а под капотом — целый ёперный театр с переездами, служебными квартирантами и мёртвыми душами, которые место занимают.