← Назад к вопросам

Как работает Memory Allocator?

1.8 Middle🔥 202 комментариев
#Основы Go#Производительность и оптимизация

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает Memory Allocator в Go

Memory Allocator (аллокатор памяти) в Go — это сложный механизм управления динамической памятью, спроектированный для эффективного выделения и освобождения памяти в многопоточных средах. Он сочетает в себе концепции из TCMalloc (Thread-Caching Malloc) от Google и собственные оптимизации Go.

Основные компоненты аллокатора

Аллокатор оперирует следующими структурными элементами:

  1. Классы размеров (Size Classes): Go не выделяет память произвольного размера. Вместо этого определены фиксированные классы размеров (например, 8, 16, 32, 48, ..., 32768 байт). Запрос округляется вверх до ближайшего класса. Это уменьшает фрагментацию и ускоряет поиск подходящего блока.

    // Пример: выделение памяти под структуру будет округлено
    type Small struct { a, b int32 } // Размер = 8 байт
    // Выделится блок из класса size=8, даже если реально нужно 8 байт.
    
  2. Спаны (mspan): Основная единица управления. Это непрерывный регион памяти (обычно 8 КБ или больше), разделённый на блоки одного класса размера. Спан служит контейнером для объектов.

  3. Кэши (mcache): Каждая горутина (G) имеет локальный кэш (mcache), содержащий список свободных спанов для каждого класса размеров. Выделение памяти из mcache не требует блокировок, что критически важно для производительности.

    // Псевдо-структура mcache (упрощённо)
    type mcache struct {
        tiny           uintptr // Кэш для мелких (<16B) внеклассовых объектов
        alloc [numSizeClasses]*mspan // Спаны для каждого класса
    }
    
  4. Центральные кэши (mcentral): Обслуживают каждый класс размеров глобально для всей программы (P — процессорная нить). Когда mcache исчерпывает свободные блоки в своём спане, он обращается к соответствующему mcentral за новым свободным спаном.

  5. Куча (mheap): Глобальная куча, управляющая всей памятью процесса. Она запрашивает большие регионы памяти у ОС (через mmap или VirtualAlloc), разбивает их на страницы и организует в спаны. Когда mcentral исчерпывает спаны, он обращается к mheap.

Принцип работы выделения памяти

Рассмотрим путь типичного вызова make или new:

obj := make([]byte, 100) // Запрос 100 байт
  1. Определение класса размера: Размер 100 байт округляется до класса 112 байт.
  2. Поиск в локальном кэше (mcache):
    *   Аллокатор проверяет горутино-локальный mcache (привязанный к текущему P) на наличие свободного блока в спане для класса 112.
    *   Если блок найден — выделение мгновенное, без блокировок.
  1. Обращение к центральному кэшу (mcentral):
    *   Если в локальном спане нет свободных блоков, аллокатор обращается к mcentral для данного класса.
    *   mcentral возвращает частично заполненный или новый спан. Эта операция защищена **мьютексом**, так как mcentral общий для всех P.
  1. Запрос у глобальной кучи (mheap):
    *   Если mcentral пуст, он запрашивает новый спан у mheap.
    *   mheap может выделить новый большой регион памяти у ОС, разметить его на спаны и добавить в mcentral.
  1. Особые случаи: Для очень больших объектов (>32 КБ) аллокатор выделяет память напрямую из mheap, минуя классы размеров и кэши (этот объект помечается как noscan для сборщика мусора).

Сборка мусора (GC) и аллокатор

Аллокатор тесно интегрирован со сборщиком мусора (Garbage Collector):

  • Цветность памяти: Аллокатор использует три-color маркировку (чёрный, серый, белый) для отслеживания живых объектов.
  • Write Barrier: При обновлении указателей во время работы программы срабатывает барьер записи, который информирует GC об изменениях в графе объектов.
  • Освобождение памяти: Когда GC определяет объект как мёртвый, его память не возвращается ОС немедленно, а помечается как свободная в соответствующем спане. Этот спан затем может быть повторно использован mcache или mcentral. Возврат памяти ОС происходит редко и большими регионами, только при значительном и длительном простаивании.

Ключевые оптимизации

  • Отсутствие блокировок на fast-path: Большинство аллокаций (малые объекты) выполняются из локального mcache без синхронизации.
  • Предвыделение (Cacheing): mcache заранее получает несколько спанов от mcentral, чтобы уменьшить частоту дорогих обращений к mheap.
  • Tiny allocator: Особый путь для объектов размером <16 байт, которые могут размещаться в одном блоке памяти для уменьшения внешней фрагментации.
  • Локализация данных: Поскольку каждая горутина работает со своим mcache, объекты, выделенные вместе, часто оказываются в одной области памяти, что улучшает кэш-локалимость CPU.

Пример в профилировщике

Используя pprof, можно увидеть работу аллокатора:

go tool pprof -alloc_objects http://localhost:6060/debug/pprof/heap

Это покажет, объекты каких размеров и где в программе создаются чаще всего, что помогает оптимизировать использование памяти.

Таким образом, Memory Allocator в Go — это высокооптимизированная система, которая балансирует между скоростью выделения (минимизируя блокировки), эффективным использованием памяти (через классы размеров) и тесной интеграцией со сборщиком мусора для автоматического управления жизненным циклом объектов.

Как работает Memory Allocator? | PrepBro