
Одной из революционных особенностей Go в сравнении с другими компилируемыми языками стало автоматическое управление освобождением памяти от неиспользуемых объектов (сборка мусора). В то же время она может привести к потере производительности при передаче контроля процессу управления памятью, но альтернативного механизма в Go представлено не было. Начиная с Go 1.20 появляется поддержка экспериментального решения для управления памятью, которое позволяет совместить безопасное выделение динамической памяти и уменьшить влияние интегрированного в скомпилированный код управления памятью на производительность приложения. В этой статье мы рассмотрим основные аспекты использования Memory Arena в Go 1.20.
Для запуска кода будем использовать актуальную на данный момент версию Go 1.20rc2, которая может быть получена из установочного пакета или через go install golang.org/dl/go1.20rc2@latest
Для включения поддержки нового механизма управления памятью добавим переменную окружения:
export GOEXPERIMENT=arenas
теперь для выделения памяти будем использовать новый модуль arena:
package main import "arena" type Person struct{ Lastname string Firstname string } func main() { mem := arena.NewArena() defer mem.Free() for i:=0; i<10; i++ { obj := arena.New[Person](mem) print(obj) } }
Как можно увидеть при запуске, адреса объектов будут выделяться последовательно из единой области памяти и (после вызова free) вся выделенная арена будет освобождаться. При правильном использовании это улучшает производительность кода, поскольку для арены не будет вызываться автоматическая сборка мусора. При необходимости копирования данных в обычный heap, может использоваться метод Clone, который создает копию структуры из арены в обычную динамическую память (например, при необходимости возврата результата обработки в основное приложение). Также в арене можно создавать слайсы с указанием начального размера и потенциальной емкости arena.MakeSlice(mem, initial, capacity).
Для выделения памяти на основе типа из reflect также можно использовать новый метод reflect.ArenaNew(mem, typ), который возвращает указатель на объект заданного типа, выделенный в арене, сохраненной в mem.
Обнаруживать ошибки при использовании арены (например, чтение или запись значения в структуру после освобождения арены) можно механизмами go run -asan (Address Sanitizer) или go run -msan (Memory Sanitizer), например:
package main import "arena" type Data struct { value int32 } func main() { mem := arena.NewArena() v := arena.New[Data](mem) mem.Free() v.value = 1 }
при запуске с asan/msan покажет ошибку некорректного использования указателя после освобождения арены.
Для хранения строк в арене можно использовать создание области памяти из последовательности байт и копировать в нее содержимое строки, например так:
src := "original" mem := arena.NewArena() defer mem.Free() bs := arena.MakeSlice[byte](mem, len(src), len(src)) copy(bs, src) str := unsafe.String(&bs[0], len(bs))
Арена также может использоваться не только для хранения структур, но и для примитивных типов данных (или их последовательностей), в этом случае взаимодействие ничем не отличается от работы с указателем на переменную:
package main import "arena" func main() { mem := arena.NewArena() defer mem.Free() v := arena.New[int32](mem) *v = 10 println(*v) }
Аналогично поведение слайсов в арене не отличается от обычных слайсов в Go:
package main import "arena" func main() { mem := arena.NewArena() defer mem.Free() v := arena.MakeSlice[int32](mem,50,100) v[49] = 10; v = append(v, 20) println(v[49]) //10 println(v[50]) //20 println(len(v)) //51 println(cap(v)) //100 }
Для обнаружения утечек памяти при использовании арены можно использовать обычные механизмы профилирования в Go (go tool pprof для визуализации сэмплирования выделения памяти, которое может быть сохранено через функции модуля runtime/pprof). С точки зрения выделения памяти работа с ареной похожа на выделение одного блока памяти (который может увеличиваться в размере) и при освобождении арены все выделенные в ней объекты становятся недоступными.
Повышение производительности можно ожидать в тех случаях, когда приложение интенсивно выделяет память (например, при хранении двоичных деревьев или иных связанных структур данных), но при этом предполагается что выделенные структуры данных являются долгоживущими и существуют до момента освобождения арены целиком (сборщик мусора для арены не применяется и выделенные объекты в последовательной области памяти не очищаются).
Статья подготовлена в преддверии старта курса Golang Developer. Professional. Приглашаю всех на бесплатный вебинар, где руководитель курса проведет собеседование выпускника программы. Реальные вопросы, комментарии по ответам, советы. Будет интересно.
