Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

CouchDB Supervisor tree