Как устроен аллокатор памяти в Go?

Ответ

Аллокатор памяти в Go — это высокопроизводительный компонент рантайма, отвечающий за управление памятью в куче (heap). Он спроектирован для эффективной работы в многопоточных приложениях с минимальными задержками.

Ключевые принципы и компоненты:

  1. Иерархия кэшей памяти: Память распределяется по трехуровневой иерархии для минимизации блокировок:

    • mcache: Локальный кэш памяти для каждого процессора (P). Горутины, выполняющиеся на одном P, могут запрашивать память из mcache без блокировок, что делает аллокацию очень быстрой.
    • mcentral: Центральный пул памяти, общий для всех P. Если в mcache заканчивается память определенного размера, он запрашивает ее у mcentral.
    • mheap: Управляет всей кучей. Если в mcentral нет свободных блоков, он запрашивает новую область памяти (арену) у операционной системы через mheap.
  2. Размерные классы (Size Classes): Объекты группируются по размеру (например, 8, 16, 32 байта). Для каждого класса размеров существует свой список свободных блоков, что позволяет избежать фрагментации и ускоряет поиск подходящего блока.

  3. mspan: Основная структура для управления памятью. Это непрерывный блок страниц памяти (обычно 8KB на страницу), который содержит объекты одного размерного класса. mspan может находиться в mcache, mcentral или mheap.

  4. Разделение на маленькие и большие объекты:

    • Маленькие объекты (< 32KB): Аллоцируются через mcache с использованием размерных классов. Это самый частый случай.
    • Большие объекты (> 32KB): Выделяются напрямую из mheap, минуя mcache и mcentral.
  5. Интеграция со сборщиком мусора (GC): Аллокатор тесно работает с GC. Когда GC освобождает объекты, память не возвращается сразу ОС, а помечается как свободная в соответствующих mspan'ах и может быть переиспользована для новых аллокаций.

Пример аллокации:

type Small struct { a, b int64 } // 16 байт
s := new(Small) // Аллоцируется в mcache для size-class 16 байт

Такая архитектура позволяет Go достигать низкой задержки при выделении памяти и высокой производительности в конкурентной среде.

Ответ 18+ 🔞

Да ты посмотри, какой у нас тут аллокатор памяти в Go, ёпта! Это ж не просто кусок кода, а целый высокопроизводительный монстр, который в куче хозяйничает. Спроектирован так, чтобы в многопоточном аду не захлебнуться и работать с минимальными задержками, блядь. Вообще красота!

На чём всё держится, или Ключевые пиздюлины:

  1. Иерархия кэшей, мать её. Трёхуровневая, чтобы все друг другу гланды не выедали:

    • mcache: Это типа личная тумбочка у каждого процессора (P). Горутина прибежала, выхватила из своей тумбочки память — и нет проблем, без всяких очередей и блокировок. Быстро, как хуй с горы!
    • mcentral: А это уже общий шкаф на всех. Если в тумбочке (mcache) пусто, бежишь к этому шкафу, откручиваешь болты (блокировки) и тащишь пачку памяти.
    • mheap: Ну а это склад, блядь, самый главный. Если в общем шкафу тоже шаром покати, тогда уже грузовик заказываем и новую партию памяти у операционки через этот mheap выбиваем.
  2. Размерные классы (Size Classes). Умнейшая хуйня! Объекты не абы как, а по размерным коробочкам рассованы: 8 байт, 16, 32 и так далее. Ищешь не просто дырку, а сразу подходящую коробку — и фрагментации нет, и находится всё моментально.

  3. mspan. Основа основ, блядь! Это как пачка листов А4, скреплённых вместе (непрерывный блок страниц памяти). На каждой такой пачке написано: «Только для объектов по 16 байт». И валяется эта пачка либо в тумбочке (mcache), либо в общем шкафу (mcentral), либо на складе (mheap).

  4. Разделение по габаритам.

    • Мелкие (< 32KB): Это наше всё, 99% случаев. Бежим к своей тумбочке (mcache), хватаем из нужной коробочки (size-class) — и готово.
    • Крупняк (> 32KB): Нафиг всю эту бюрократию, блядь! Такие чудовища выделяем сразу со склада (mheap), минуя и тумбочку, и шкаф.
  5. Работа с мусорщиком (GC). Тут вообще симбиоз, ёпта! Мусорщик пришёл, почистил — память не выкидывается на помойку ОС, а аккуратно кладётся обратно в те же самые пачки (mspan) и помечается «свободно». Чтобы потом снова использовать. Экономия, блядь!

Пример, чтобы не быть пиздаболом:

type Small struct { a, b int64 } // 16 байт, классика
s := new(Small) // Вжух! И этот мелкий пиздёныш уже сидит в mcache в коробочке для 16-байтных объектов.

Вот такая, блядь, хитрая архитектура. Из-за неё аллокация в Go быстрая, как ядрёна вошь, и даже когда горутин — овердохуища, они друг другу не мешают. Красота, да и только, в рот меня чих-пых!