Как стать автором
Обновить

Комментарии 15

Спасибо за интересную и познавательную статью!

Последний метод — Clone, который позволяет забрать что-то из арены обратно в heap даже после удаления. Ведь если обратиться к памяти арены после ее освобождения, данные будут битыми и все сломается.

Имеется ввиду, что забрать в хип можно до освобождения арены и эти данные будут доступны после освобождения арены, так?

Да, всё верно. После free обращаться к переменным из арены нельзя. Если же после использования арены нужно забрать какой‑то результат, то clone позволяет скопировать память из арены в хип.

Хорошая статья, толковая. Спасибо!

Это в Java выделение объектов практически всегда приводит к их выделению в heap. В Go большинство короткоживущих объектов останутся на стеке и очистятся автоматически.

Смелое заявление. А давайте я скажу наоборот: escape analysis в Go - детская поделка на фоне того что умеет JVM.

Да, за счет JIT и большей истории escape analysis в Java бодрее, чем в Go. Но и там, и там далек от совершенства.

Возможность работы с сущностями как значениями — полезная особенность, которая частично нивелирует отсутствие «поколений» в сборщике мусора. Но об этом часто забывают.

Смелое заявление. А давайте я скажу наоборот: escape analysis в Go - детская поделка на фоне того что умеет JVM.

До JIT ещё надо дожить, а с учётом числа магии, которую использует тот же Spring под капотом, JIT ещё и очень консервативно может подойти к вопросу о размещении переменных.

Но дело даже не в этом. В отличие от JVM-языков, Go — это язык, где значения, а не ссылки, правят бал. Слайс структур будет находиться в непрерывной памяти, а сами структуры с такими же полями будут представлены как непрерывный кусок в памяти. Всё это приводит к тому, что Go, при правильном использовании, генерирует меньшее число объектов на хипе. И даже наши дженерики следуют этому правилу — за счёт отсутствия type erasure я могу быть уверен, что в таком коде:

type MyCollection[T any] struct {
   slc []T
}

Итоговая коллекция будет работать именно со значениями, если я явно не запросил указатели или типы, содержащие указатели.

И да, я в курсе про Project Valhalla. В курсе про него я с 2016 года, а воз и ныне там.

P.S. А ещё у нас List[MyType] и List[MyAnotherType] — это разные типы, которые видны рантайму в момент исполнения. :)

Вы вот это всё красиво рассказываете, только мы находимся в комментах статьи с текстом следующего содержания:

Особенность GRPC в том, что при парсинге протобафа в памяти возникает огромное количество мелких объектов, которые потом ложатся грузом на сборщик мусора.

GRPC — это основной протокол, который используют в Google для разработки своих микросервисов. Да и в целом по рынку GRPC в Go значительно популярнее остальных протоколов вроде REST.

Чего же GRPC на value-типах не сделали, а?

Ну давайте детально. Начну с конца.

Да и в целом по рынку GRPC в Go значительно популярнее остальных протоколов вроде REST.

Это неправда, я не знаю, откуда автор это взял. Основным протоколом по-прежнему остается HTTP+JSON практически во всех компаниях. И тут есть довольно понятный ответ: gRPC до сих пор не подружили с вебом. Да, есть всякие библиотеки от энтузиастов (последнюю, кстати, мы используем, но мы исключение, и у нас довольно специфичный стек). Но вот коробочного решения пока нет, поэтому делать сервис, который общается с пользователями на основе gRPC, не самая лучшая идея. А как гонять трафик между микросервисами — это уже отдельный вопрос

Особенность GRPC в том, что при парсинге протобафа в памяти возникает огромное количество мелких объектов, которые потом ложатся грузом на сборщик мусора.

Это правда, но не вся. Стандартный декодер gRPC очень активно использует reflect для кодирования и декодирования. Тут любой язык начнёт складываться, не говоря уж про то, что правила простановки полей из reflect довольно специфичные. Я не виню разработчиков gRPC, что они не стали вникать и сделали как проще. Но есть сторонние библиотеки, которые работают на основе кодогенерации, и у них ситуация намного лучше.

Чего же GRPC на value-типах не сделали, а?

Не было дженериков, из-за этого пришлось использовать указатели там, где их по-хорошему использовать не надо (у gRPC все поля optional, и возникает вопрос, как передать разницу между пустой структурой и структурой, которую не прислали). Это раз. Во-вторых, либу отвечающую за протобаф и gRPC писала (как минимум вначале) команда, которая была абсолютно не знакома с языком. По этой же причине Kubernetes, хоть и является крутым продуктом, но код его не сильно идиоматичный, ведь это перенос прототипа, который был написан на Java.

Спасибо. Fixed.

Проблема кастомных аллокаторов, основанных на чём-либо, кроме того, о чём знает рантайм (включая вызов unix.Mmap), заключается в том, что внутри такой памяти нельзя размещать объекты из Go-хипа. Т.е. если у вас указатели, интерфейсы, слайсы, то без плясок с бубном разместить инстансы таких типов внутри объектов, которые лежат в памяти, полученной от аллокатора, не получится. А в случае мап и каналов не получится вообще.

Арены в текущем варианте в стандарт скорее всего не попадут и на то есть довольно серьёзная причина: они загрязняют сигнатуру функций. Получается ситуация, похожая с контекстами, где функция, принимающая контекст, приводит к тому, что все вызывающие её по цепочке функции тоже принимают контекст.

Но сама идея арен интересна, и рантайм Go, судя по дизайн-доку, должен ловить use-after-free баги и убивать программу. Просто текущий прототип этого не делает.

The implementation is required to cause a run-time erro and terminate the Go program if the application accesses any object whose memory has already been freed.

А sync.Pool, кстати, должен хорошо себя показать с итераторами. Т.к. можно после окончания работы итератора прозрачно для пользователя возвращать коллекцию обратно в пул.

Чем больше мусора, тем больше CPU мы тратим

Не знаю, как обстоит дело в go, но в java, например, это не так. Если взять любой коллектор на основе поколений, ему все равно сколько у вас мертвых объектов, поскольку он обойдет граф по живым, скопирует их все в соседний пустой регион (survivor space 1,2), а дальше просто объявит исходный регион пустым. Если бы это так не работало, то создавать что-то в куче было бы дорого и все поголовно пользовались бы пулами даже для простых объектов. Так делают крайне редко именно потому, что короткоживущие объекты на gc почти не влияют

Возможно имелось ввиду что GC будет чаще вызываться, если аллоцировать миллионы объектов и на это будет уходить CPU

Казалось бы тогда автор бы так и написал, называть аллокации мусором не очень корректно, даже если через некоторое время они таковым станут (а может и не станут, если у вас какой-нибудь кэш) :) Но даже в этом случае (опять же речь про java) аллоцировать объекты это не так дорого, как может показаться - в большинстве коллекторов объекты создаются в специальной области (eden), в которой отсутствует фрагментация, объекты создаются просто последовательно занимая память, что само по себе довольно быстро. Проблемы начинаются тогда, когда eden заполнится и нужно будет его очистить, а для этого в свою очередь нужно пройтись по графу живых объектов и скопировать их в соседнюю область, очистив попутно eden. Это уже дольше и опять же зависит не от количества мусора, а от количества живых объектов. Единственное на что существенно влияет "мусор" - это скорость с которой заполняется eden, т.е. как часто нужно вызывать gc алгоритм и копировать живые объекты. Но и тут - если мы признали, что бОльшая часть объектов попадающих в eden на момент сборки будет мусором, то значит копировать потребуется не так много объектов и происходить это будет тоже недолго.

Да и в целом по рынку GRPC в Go значительно популярнее остальных протоколов вроде REST.

правда что ли?

а в остальном познавательно, спасибо.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий