Какие преимущества дает сборка мусора в Erlang

Одна из особенностей языка Erlang, которая дает ему преимущества, — это процесс управления сборкой мусора. Инженеры Evrone активно используют Erlang в проектах компании.

О том, как происходит сбор мусора, рассказал ведущий разработчик нашей компании Борис Кузнецов в докладе на главной ежегодной конференции Erlang сообщества: CodeBeam STO. Давайте разберемся!

Эволюция сборщика мусора

Erlang — это функциональный язык программирования с динамической типизацией. Его главной особенностью является работа на уровне отдельных процессов. Процессоры компьютеров практически перестали развиваться в сторону увеличения частоты, и рост производительности идет за счет увеличения количества ядер. Программы, написанные на Erlang, как раз позволяют использовать всю мощность многоядерных процессоров.

Применение этого языка программирования актуально при разработке бэкенда для отказоустойчивых веб-приложений, приложений с обменом сообщениями, диспетчеров событий, систем мониторинга серверов.

Процессы в Erlang

Центральная точка текущей реализации архитектуры выполнение кода — планировщик, который стартует на каждом ядре операционной системы с индивидуальной очередью запуска процессов. На каждый процесс отведен определенный лимит (количество вызовов функций), в рамках которого процесс выполняет свою работу, после чего начнется выполнение следующего в очереди.

Процесс в виртуальной машине Erlang — полностью изолированная сущность, которая не связана с процессами ОС и имеет свою собственную память, в ней размещается стек (инструкции по работе процесса) и куча (heap - данные, необходимые для работы процесса).

Процесс состоит из двух частей:

  • Отдельный блок-контроллер, содержащий общую информацию о процессе (ссылки на область памяти, статистика, счетчики и т. д.).
  • Область памяти, на которую ссылается контроллер процесса.

Когда нужен сборщик мусора

Когда не хватает места для размещения нового объекта, запускается сборщик мусора (Garbage Collector, GC), который уничтожает ненужные для дальнейшего выполнения программы данные, после чего код продолжает работу.

В Erlang используется GC, разделяющий объекты в куче на поколения молодые и те, которые пережили хотя-бы два цикла сборки мусора (долгоживущие). Долгоживущие объекты обрабатываются сборщиком мусора примерно раз в 65 тысяч циклов.

Стандартный цикл

Малый цикл Garbage Collector выделяет в current heap объекты, на которые больше нет ссылок и которые не нужны для продолжения работы процесса. Процедура происходит в несколько этапов:

  • Сборщик находит корневые объекты (в терминологии Erlang — root objects), то есть различные места, которые могут ссылаться на данные в памяти процесса (словарь процесса, очередь сообщений, данные об ошибке и т. д.). GC переносит в новую область памяти только те объекты, на которые кто-то ссылается.
  • Затем сборщик проходится по перенесенным объектам в новой области памяти и переносит все данные, на которые они ссылаются.
  • В новую область переносится стек, после чего цикл сборки мусора завершается, а объекты, на которые не осталось ссылок, уничтожаются. Пережившие процедуру данные помечаются отдельной меткой, чтобы при следующем цикле сборки мусора перенестись в область памяти для взрослых объектов (то есть тех, что пережили хотя бы два цикла) — old heap.

Стоит отметить, что в памяти процесса живут сразу стек (инструкции выполнения) и куча (данные для работы), они располагаются по краям доступного отрезка памяти и растут навстречу друг к другу. Как только свободного места не остается, планировщик прерывает выполнение кода на данном ядре и вызывается сборщик мусора, который выделяет новую область памяти и переносит в нее только актуальные объекты.

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

Что делать со старыми объектами

Особенность Erlang в том, что сборщик мусора возвращается к объектам старого поколения (major collection, живут в отдельной области памяти) лишь через ~ 65 тыс. стандартных циклов. В ходе этого процесса сборщик мусора выделяет новую область памяти и переносит в нее стек, актуальные объекты из области памяти для долгоживущих объектов и молодые объекты из текущей памяти процесса.

Так как за один раз обрабатывалось большое число объекта, в редких случаях этот процесс мог занимать продолжительное время (больше 1мс), что негативно влияло на планировщик процессов, так как он не мог учитывать данное время в своей логике, так как устанавливает лимит выполнения только на количество вызванных функций. Раньше в Erlang существовал баг, который мог привести к краху процесса, если он выполнялся слишком долго. Причиной могли быть либо долгая сборка мусора, либо вызов NIF (native implemented functions, внешних библиотек).

Для решения этих проблем был добавлен второй тип планировщика для каждого ядра — Dirty scheduler. Он также запускается на каждом ядре ОС как и обычный, но такой тип планировщика использует общую очередь на все ядра. В нее попадают процессы, которые ожидаемо могут долго выполняться, обходя лимиты выполнения (NIF либо долгий цикл сборки).

Если система обнаруживает, что в области памяти не хватает места, а следующий вызов GC может потенциально занять много времени, то процесс будет помечен на перенос в dirty job queue и ему будет выделен небольшой участок памяти необходимый для того, чтобы процесс продолжил свою работу и израсходовал доступный ему лимит на выполнение.

Чтобы предоставить процессу необходимую прямо сейчас память (так как у него еще остался лимит выполнения), используется сборка мусора с задержкой (Delay GC). Технология помечает текущую кучу как abandoned heap и выделяет новую область, в которой будет доступно необходимое процессу место под новые объекты с небольшим запасом.

Если процесс израсходует выделенную область и у него останется лимит на выполнение, то ему будут добавляться еще новые области пока у процесса будет оставаться лимит на выполнение (reductions). В терминологии исходного кода сборщика мусора, такие области памяти называются heap fragments.

Рост памяти

По каким правилам растет память процесса? В текущей, 21-й версии Erlang это выглядит так:

  • Первые 23 увеличения происходят по Фибоначчи с исходными числами 12 и 38.
  • После 23: увеличение размера памяти процесса на 20%.

После того, как в новую область памяти перенесли актуальные объекты, может оказаться что их стало значительно меньше чем ожидалось. В таком случае происходит уменьшение выделенного фрагмента по следующему правилу — берется большее значение из чисел: 1/8 от размера old heap или в три раза больше, чем необходимо.

При главном цикле сборки мусора, которые затрагивает все области памяти процесса (с молодыми и долгоживущими объектами), изначально выделяется требуемое пространство (сумма всех фрагментов + запрашиваемое место). После сборки происходит корректировка размера, в ходе которой если он превышает в четыре необходимое место под актуальные объекты, то размер уменьшается до двукратного, то есть в 2 раза больше чем необходимо.

Идеально для высококонкурентных программ

Как видим из этого примера, актуальность объектов при сборке мусора можно проверять без остановки целой системы, как это происходит в других популярных языках программирования, например в Ruby или Python. Этому способствует организация работы процессов как изолированных частей системы.

Виртуальная машина Erlang является достаточно зрелым программным продуктом, проверенным временем в системах мягкого realtime. Erlang дает преимущества при создании надежных, высококонкурентных распределенных программ в сфере телефонии, передачи потокового видео, мобильного интернета, сервисов обмена сообщениями и так же в различных веб-проектах.

0
12 комментариев
Написать комментарий...
Сергей Токарев

немного необычно для VC, но прочитал с удовольтствием

думаю, тут достаточно технарей, чтобы оценить

Ответить
Развернуть ветку
Nikita Kriuchkov

А можно поинтересоваться, что Вам понравилось? Ужасно ведь изложено. Для сравнения https://hamidreza-s.github.io/erlang%20garbage%20collection%20memory%20layout%20soft%20realtime/2015/08/24/erlang-garbage-collection-details-and-why-it-matters.html или https://ru.wikipedia.org/wiki/Erlang#%D0%9E%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BE%D1%81%D0%BE%D0%B1%D0%B5%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D0%B8

Да, Erlang крут для создания распределённых вычислительных систем построенных на модели акторов. Но статья ведь не об этом.

P.s. Самый лучший вариант, на мой взгляд, http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.7791&rep=rep1&type=pdf

Ответить
Развернуть ветку
Сергей Токарев

я на другом стеке вообще сижу, на языке с GC, поэтому для поверхностного ознакомления с другими технологиями счел достаточным. Даже не Эрланг как таковой интересен, а логики разных GC

Ответить
Развернуть ветку
Nick Sidorov

Да наконец-то на VC что-то для технарей появилось!
Знаете как тяжело смехуечки для большинства статей программисту выдумывать?!

Ответить
Развернуть ветку
Anton Kozlov

3 ссылка на общее описание алгоритма, 2 на википедию, где вообще 0 информации о работе GC в erlang, только 1 ссылка на ту же тему, но тоже на английском, вы точно уверены что там лучше изложено?

А по статье - хорошо, конечно, но имхо сайт не тот, это формат хабра, скорее.

Ответить
Развернуть ветку
Nikita Kriuchkov

Прежде, чем опубликовать ссылки я прочел содержимое. Там не на эльфийском написано. Я не утверждал, что содержимое этих статей один в один, я предложил для сравнения подачу материала.

И да, я считаю, что авторы Erlang'а Robert Virding и Joe Armstrong лучше изложили то, как построен их GC.

Ответить
Развернуть ветку
FN

Это не для технарей написано, а для домохозяек. То есть как раз попали в аудиторию.

Ответить
Развернуть ветку
Сергей Токарев

как говорил Эрлих Бахман:
- ну и самомнение

Ответить
Развернуть ветку
Voronezhskiy Yury

Спасибо ;) просвещаем менеджмент ;)

Ответить
Развернуть ветку
N0FaceMan

А для каких целей используете erlang в компании?

Ответить
Развернуть ветку
Evrone
Автор

Для написания микросервисов, также как Go и Rust

Ответить
Развернуть ветку
N0FaceMan

Я так понимаю, чистый erlang, не elexir?

Ответить
Развернуть ветку
9 комментариев
Раскрывать всегда