Одной из революционных особенностей 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. Приглашаю всех на бесплатный вебинар, где руководитель курса проведет собеседование выпускника программы. Реальные вопросы, комментарии по ответам, советы. Будет интересно.
Комментарии (30)
danslapman
11.01.2023 20:44+12Одной из революционных особенностей Go в сравнении с другими компилируемыми языками стало автоматическое управление освобождением памяти от неиспользуемых объектов
Вы это серьёзно? Языки с GC были задолго до go
Scratch
11.01.2023 21:20+1ключевое слово - компилируемыми. Приведёте пример языка, который генерит самодостаточный экзэшник с GC внутри?
danslapman
11.01.2023 21:31+5Конечно: Standart ML/OCaml, Haskell, Eiffel, да даже в Ada была ограниченная сборка мусора. Rust, кстати, тоже раньше go начали разрабатывать
Desprit
11.01.2023 22:29А rust здесь каким боком?
danslapman
11.01.2023 22:36RC/ARC можно считать простейшим способом сборки мусора (но конечно же это не полноценный GC в обычном понимании)
Kelbon
11.01.2023 22:51тогда вы явно забыли добавить в список С++, откуда shared_ptr и был скопирован растом
SabMakc
11.01.2023 22:44+3А причем компиляция и самодостаточность екзешника?
Java и C# вполне компилируются, пускай и в промежуточное представление.Free_ze
12.01.2023 23:40+2К слову, дотнет может собираться в единый исполняемый файл с рантаймом внутри.
hello_my_name_is_dany
13.01.2023 13:24А в новой версии .NET c ограничениями, но можно даже сразу в машинный код
Kelbon
11.01.2023 22:46+3Джава компилируемый язык, там гц. И ещё миллиард языков. Хаскель также компилируемый с гц.
Это не то что не революция, это даунгрейд.
А приведённый "отличный новый код" это просто ужас, код на С и то лучше чем на go.
Дизайнеры go буквально сделали сишку с сборкой мусора и без единого плюса сишки
Vadim_Aleks
12.01.2023 17:12+1А приведённый "отличный новый код" это просто ужас, код на С и то лучше чем на go.
Вам не предлагают отныне и навсегда пользоваться arena-аллокаторами и забыть про старый API. Важно понимать, что арена-аллокаторы нужны не всем. Любой низкоуровневый код выглядит уродливо, будь то Java, Haskell, Go, C
Arturius92
12.01.2023 19:14Подскажите пожалуйста: если всю динамическую память выделяем через arena, то сборщик мусора тогда вообще не запускается?
Devoter
13.01.2023 14:30Есть подозрения, что именно так и сделают в tinygo. А вообще, довольно странный ход. Видимо, в следующей итерации добавят возможно писать и выбирать аллокаторы памяти и будет почти C, только больше кода, почти C++, только меньше возможностей. Для меня было бы киллер-фичей возможность запускать горутины при помощи аллокатора и убивать их при необходимости.
miga
Ожидание: программы на го станут быстрее, сохраняя безопасное управлению памятью в бОльшей части кодовой базы
Реальность: программы на го текут пуще прежнего, тонны новых use-after-free/buffer overruns
/s
SabMakc
Ладно утечки... Лично меня больше напрягают битые ссылки, которые выявляются только при запуске со специальными флагами...
А там и гонки - что быстрее? Переменная прочтется или перезапишется?
miga
ооо, гонок в голанге и без арен хватает :) ref/mem написан кровью программистов
Vadim_Aleks
В смысле nil-указатели? Так используйте меньше указателей. Часто вижу []*Object и прочее таскание указателей по всем функциям, из-за чего страдает и программист, и GC)
По части nil-указателей у меня лишь болит от использования неинициализированной map'ы. Так легко объявить и так легко забыть инициализировать)
SabMakc
Битые ссылки - это не nil-указатели, а указатель на область памяти, где раньше была эта переменная, а потом ее собрал сборщик мусора и пометил область свободной для заполнения снова...
В итоге к моменту чтения, по старому адресу уже совсем другие данные могут оказаться...
Проблема в том, что возникает плавающая бага, которую очень сложно диагностировать (в любом языке)...
Vadim_Aleks
А как это можно воспроизвести без cgo и unsafe?
muturgan
С помощью арены?
Vadim_Aleks
А, речь была про битые ссылки после введения арен, теперь понял посыл
Vadim_Aleks
А что именно текло: приложения или библиотека? У меня Go-приложения текли только когда неправильно использовался unsafe)
miga
Течет не напрямую память (хотя и с ней до того, как начали использовать MADV_FREE были приколы), а горутины, которые тем или иным способом не умирают и коптят на каком-нибудь канале до самой смерти процесса, вместе со всеми своими ресурсами
gudvinr
Могут довольно интересно течь.
Например через горутины, которые не завершаются.
Ещё если у вас есть массивы и вы их переиспользуете, то у них capacity не меняется, а меняется только длина, соответственно, в памяти такой массив будет занимать много места, т.к. лишние элементы просто скрыты между len и cap и GC их не будет удалять.