Erlang Q&A
Атомы
Сколько атомов может создать Erlang процесс?
Небольше чем заданный при запуске виртуальной машины BEAM размер глобальной таблицы атомов. По умолчанию таблица вмещает 1_048_576 атомов. https://www.erlang.org/doc/system/system_limits.html#atoms
Атомы хранятся в глобальной таблице, которая разделяется всеми процессами виртуальной машины BEAM.
Размер таблицы можно увеличить при старте BEAM (опция командной строки erl +t).
Таблица очищается при завершении BEAM и не очищается сборщиком мусора во время выполнения.
Нужно избегать динамического порождения атомов (list_to_atom/1, binary_to_atom/1).
Как узнать предельное количество атомов в глобальной таблице?
Функция erlang:system_info(atom_limit)
atom_count- количество атомов в таблице текущего узлаatom_limit- максимальное количество атомов в таблице текущего узла
Размер памяти занятый атомами:
erlang:memory(atom)- размер памяти занятой атомами и резервом места под нихerlang:memory(atom_used)- размер памяти занятой атомами
Как переполнить таблицу атомов?
При переполнении таблицы выводится сообщение "no more index entries in atom_tab (max=1048576)",
виртуальная машина аварийно завершает работу "Crash dump is being written to...".
-module(atoms).
-export([gen/1]).
gen(N) when is_integer(N), N > 0 ->
mem_usage("Before"),
gen_rec(N),
mem_usage("After").
gen_rec(1) -> create_atom(1);
gen_rec(N) when is_integer(N), N > 1 ->
create_atom(N - 1),
gen_rec(N - 1).
create_atom(N) ->
try
AtomName = "atom_" ++ integer_to_list(os:system_time(microsecond)) ++ "_" ++ integer_to_list(rand:uniform(1000000)),
list_to_atom(AtomName)
catch
error:Reason -> io:format("Can not create atom: N = ~p (~p)~n", [N, Reason])
end.
mem_usage(Str) ->
io:format("~p: atoms table: reserved ~p atoms, used ~p atoms, memory reserved + used ~.2f MiB, memory used ~.2f MiB~n",
[Str, erlang:system_info(atom_limit), erlang:system_info(atom_count),
erlang:memory(atom) / 1024 / 1024, erlang:memory(atom_used) / 1024 / 1024]).
Компилируем и запускаем генерацию > 2_000_000 атомов:
$ erlc ./atoms.erl
$ erl +t 2000000 -eval "atoms:gen(2_900_000)." -noshell -s init stop
Списки
Как реализованы списки в Erlang?
Тип list в Erlang - это базовый тип поддерживаемый виртуальной машиной BEAM. Он реализован как односвязный список (singly linked list), в котором каждый элемент (cons cell) содержит указатель на голову (head, car) и указатель на хвост (tail, cdr) - следующий элемент в списке. Реализация на C:
struct cons_cell {
Eterm *head; /* Указатель на значение (value) */
Eterm *tail; /* Указатель на следующий элемент */
};
- The BEAM Book: Understanding the Erlang Runtime System. "Chapter 4.3. Lists" // https://blog.stenmans.org/theBeamBook/?ref=crustofcode.com#_lists
Каждая ячейка (cons cell) занимает два машинных слова BEAM.
Список из N элементов занимает минимум 2 * N машинных слов для указателей head и tail. Если элемент помещается в машинное слово, то его значение хранится в head вместо указателя. Например, список из 1 миллиона целых чисел (small integers) требует 1 миллиона cons ячеек - на 64-битной машине это 2 * 8 * 10^6 байт.
Операции добавления элементов:
- Prepend: добавления элемента Head в начала списка
Tail(prepend, cons):[Head | Tail], требует только корректировки указателяtailэлементаHead, время выполнения O(1) - Append: конкатенация списков
L3 = L1 ++ L2,L3 = lists:append(L1, L2): выполняет копирование элементов L1 и связывает последний элемент со головой L2, время выполнения O(|L1|).
Чем отличаются [1, 2 | [a, b, c]] и [1, 2] ++ [a, b, c]?
- (быстрее) Выражение
[1, 2 | [a, b, c]]- это операция prepend, связывает указательtailэлемента 2 с головой списка[a, b, c], выполняется только корректировка указателя за время O(1). - (медленнее) Выражение
[1, 2] ++ [a, b, c]- это операция append; создается копия списка[1, 2], элемент 2 нового списка связывается с головой списка[a, b, c], необходимо выполнить копирование первого списка и откорректировать указатель.
Где хранятся списки и чем ограничена их длина?
Списки хранятся в куче (heap) процесса. Длина списка ограничена доступной памятью для кучи.
По умолчанию куча может расти до ограничений BEAM и операционной системы. Сборщик мусора (garbage collector, GC) увеличивает кучу по мере необходимости. Ограничения на размер кучи для процесса можно установить через вызов функции process_flag(max_heap_size, ...).
Дерево супервизоров распределенной СУБД CouchDB
CouchDB — документо-ориентированная распределенная система управления базами данных с открытым исходным кодом (NoSQL). Одной из особенностей является поддержка репликации с несколькими ведущими узлами (multi-primary, multi-master).
https://github.com/apache/couchdb
На основе CouchDB построены: IBM Cloudant, СУБД Енисей.
Запуск Erlang VM (BEAM)
Когда CouchDB запускается из релиза (например, _rel/bin/couch start), Erlang VM (BEAM) инициализирует базовую среду и запускает
два критчески важных приложения, без которых не может работать ни один Erlang-узел: kernel и stdlib.
kernelотвечает за базовые сервисы: загрузку кода, логирование, работу с файловой системой, управление приложениями.stdlib- стандартная библиотека Erlang/OTP.
Старт kernel и создание контроллера приложений
В процессе запуска приложения kernel его внутренняя логика создает процесс, который регистрируется под именем application_controller.
Он существует с момента старта узла до его остановки.
Запуск приложения CouchDB
Для запуска приложения couch (src/couch/src/couch.app.src) контроллер application_controller выполняет следующие действия:
- запускает приложения из списка зависимостей приложения couch:
{applications, [kernel, stdlib, ..., ibrowse, mochiweb, ...]} - из стартового модуля вызывает
couch_app:start/2, который создает дерево супервизоров (couch_sup:start_link()).
Дерево супервизоров CouchDB
