Введение в ARC/ORC в Nim

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Nim переходит к более эффективным моделям управления памятью: ARC и ORC. Давайте узнаем, как именно они изменят работу с памятью в нём.



Введение


Всем привет! В этой статье я постараюсь рассказать, что такое ARC и ORC и как они повлияют на производительность или другие части Nim'а. Я не буду глубоко погружаться в аспекты программной части, а постараюсь дать более или менее высокоуровневое объяснение.


Давайте начнём издалека: Nim всегда был языком со сборщиком мусора (GC). Конечно же GC можно выключить, но тогда при работе с большей частью стандартной библиотеки (а она немаленькая) память будет утекать.


Стандартным GC в Nim уже долгое время является refc (отложенный подсчёт ссылок с mark & sweep фазой для сборки циклов), хотя доступны и другие варианты, как markAndSweep, boehm, go, regions.


За последние несколько лет у разработчиков Nim'а появилось несколько разных идей, связанных с деструкторами, собственными ссылками (owned ref) и так далее:


  • https://nim-lang.org/araq/destructors.html
  • https://nim-lang.org/araq/ownedrefs.html

Из симбиоза этих идей получилось то, что в Nim называется ARC


Что такое ARC?


По своей сути ARC это модель управления памятью, основанная на автоматическом подсчёте ссылок (Automatic Reference Counting) с деструкторами и семантикой перемещений (move semantics). Можно заметить то, что ARC в Nim называется так же, как ARC в Swift, но есть одно больше различие — в Nim ARC не использует атомарный подсчёт ссылок.


Подсчёт ссылок остаётся одним из самых популярных алгоритмов для освобождения неиспользуемых ресурсов программы. Счётчик ссылок для любой управляемой (контролируемой runtime) ссылки показывает количество раз, которые ссылка используется в других частях программы. Когда этот счётчик становится нулевым, все данные по этой ссылке освобождаются.


Основным отличием ARC от остальных GC в Nim является то, что ARC полностью детерминированный — компилятор автоматически вставляет деструкторы в программу для удаления переменных (строк, последовательностей, ссылок, и т.д), которые больше не нужны. В этом смысле ARC похож на C++ с его деструкторами (RAII)


Для того, чтобы продемонстрировать этот процесс, мы используем интроспекцию ARC expandArc, которая доступна начиная с Nim 1.4.


Возьмём простой пример кода на Nim:


proc main = 
  let mystr = stdin.readLine()

  case mystr
  of "привет":
    echo "Здравствуйте!"
  of "пока":
    echo "Удачи!"
    quit()
  else:
    discard

main()

И используем эту интроспекцию командой nim c --gc:arc --expandArc:main example.nim.


--expandArc: main

var mystr
try:
  mystr = readLine(stdin)
  case mystr
  of "привет":
    echo ["Здравствуйте!"]
  of "пока":
    echo ["Удачи!"]
    quit(0)
  else:
    discard
finally:
  `=destroy`(mystr)
-- end of expandArc ------------------------

Результат этой интроспекции довольно интересен — Nim завернул тело процедуры main в try: finally выражение (код в finally выполняется всегда, даже если внутри блока try было вызвано исключение) и вставил вызов =destroy для строки mystr, чтобы она уничтожилась после окончания её жизненного цикла.


Благодаря этому мы можем увидеть одну из главных возможностей ARC: управление памятью на основе областей видимости (scope-based MM). Область видимости — это любой отдельный регион кода в программе. Такое управление памятью означает, что компилятор автоматически вставит вызовы деструкторов везде, где это необходимо, после выхода из области видимости. Многие части Nim'а вводят новые области видимости: процедуры, функции, конвертеры, методы, конструкции с block, циклы for и while и так далее.


У ARC к тому же имеются так называемые hooks — специальные процедуры, которые можно привязывать к типам для того, чтобы перезаписать стандартное поведение компилятора при деструкции/перемещении/копировании переменной. Они являются очень полезными при создании нестандартных семантик для своих типов, работы с низкоуровневыми операциями, включающими сырые указателями, или для FFI.


По сравнению с refc ARC обладает немалым количеством преимуществ (включая упомянутые выше):


  • Управление памятью на основе областей видимости (деструкторы вставляются после областей видимости) — уменьшает потребление памяти программой и улучшает производительность.


  • Семантики перемещений — возможность компилятора статически анализировать программу и переводить копии в перемещения там, где это возможно.


  • Общая куча — в отличие от refc, у которого куча отдельная для каждого потока (thread-local heap), в ARC потоки имеют доступ к одной и той же памяти. Благодаря этому не нужно копировать переменные между потоками — вместо этого их можно перемещать. Так же стоит обратить внимание на RFC об изоляции и отправке данных между потоками, которое строится на основе ARC.


  • Упрощение работы с FFI — к примеру, с refc необходимо явно инициализировать его в каждом "чужом" (т.е. не созданным в самой программе) потоке, что не нужно для ARC. Это так же означает, что ARC является намного лучшим выбором для создания общих библиотек, которые будут использоваться из других языков (.dll, .so, нативные модули для Python'а и так далее)


  • Подходит для программирования в системах реального времени — hard realtime


  • Избавление от копий (copy elision), в Nim так же называется как вывод курсоров (cursor inference) — позволяет компилятору заменять копии простыми курсорами (алиасами) в большом количестве случаев



В целом, ARC для программ на Nim является отличным шагом вперёд, делающим их быстрее, уменьшающим потребление памяти, и давая им предсказуемое поведение.


Для того, чтобы включить ARC для вашей программы, всё, что нужно сделать, это скомпилировать её с ключом --gc:arc, или добавить его в конфигурационный файл вашего проекта (.nims или .cfg).


Проблема с циклами


Но подождите! Разве мы что-то не забыли? ARC проводит подсчёт ссылок, и, как известно, подсчёт ссылок не может освобождать циклы. Цикл — это отношение нескольких объектов, когда они все зависят друг от друга, и эта зависимость замкнута. Возьмём простой пример цикла: 3 объекта (A, B, C), у каждого их которых есть ссылка на другой объект, создают такую зависимость:



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


В Nim'е сборка циклов совершалась mark & sweep фазой refc GC, но лучше использовать ARC как основу для создания чего-то лучшего. Это приводит нас к:


ORC — сборщик циклов для Nim


ORC является совершенно новым сборщиком циклов, основанным на ARC. Его можно считать полноценным GC, так как он включает в себя фазу локального отслеживания (local tracing phase) в отличие от большинства других отслеживающих GC, которые проводят глобальное отслеживание (global tracing).


Для работы с async в Nim необходимо использовать ORC, потому что асинхронность в Nim'е образует циклы, которые необходимо собрать.


ORC сохраняет большую часть преимуществ ARC кроме детерминированности (частично) — по умолчанию у ORC есть адаптивный лимит для сборки циклов, и hard realtime (тоже частично) — по той же самой причине.


Для включения ORC вам нужно компилировать вашу программу с --gc:orc, но планируется, что в будущем ORC станет стандартным GC в Nim'е


Я заинтересовался! Как мне можно их протестировать?


Ничего сложного — вам всего лишь нужно поставить последнюю версию — Nim 1.4. Возможно эта версия уже доступна в ваших пакетных менеджерах.


Это всё! Спасибо за чтение данной статьи — я надеюсь, что она вам понравилась!


Источники / дополнительная информация:


  • Introducing --gc:arc
  • Update on --gc:arc
  • New garbage collector --gc:orc is a joy to use.
  • Официальная документация по деструкторам и семантикам перемещений
  • FOSDEM 2020 — семантики перемещений для Nim
  • NimConf 2020 — Nim ARC/ORC
  • Сообщество Nim'а
  • RFC: Объединить опции GC/управления памятью в Nim
Источник: https://habr.com/ru/post/523674/


Интересные статьи

Интересные статьи

Всем привет! Не так давно на работе в рамках тестирования нового бизнес-процесса мне понадобилась возможность авторизации под разными пользователями. Переход в соответствующий р...
VUE.JS - это javascript фрэймворк, с версии 18.5 его добавили в ядро битрикса, поэтому можно его использовать из коробки.
История сегодня пойдёт про автосервис в Москве и его продвижении в течении 8 месяцев. Первое знакомство было ещё пару лет назад при странных обстоятельствах. Пришёл автосервис за заявками,...
Однажды, в понедельник, мне пришла в голову мысль — "а покопаюсь ка я в новом ядре" (новым относительно, но об этом позже). Мысль не появилась на ровном месте, а предпосылками для нее стали: ...
Несмотря на то, что “в коробке” с Битриксом уже идут модули как для SOAP (модуль “Веб сервисы” в редакции “Бизнес” и старше), так и для REST (модуль “Rest API” во всех редакциях, начиная с...