Go 1.20 и арена памяти
Одной из революционных особенностей 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. Приглашаю всех на бесплатный вебинар, где руководитель курса проведет собеседование выпускника программы. Реальные вопросы, комментарии по ответам, советы. Будет интересно.