Написал свою первую NIF-библиотеку
27 декабря 2012
Недавно мне потребовалось определить load average системы из Erlang. Готового решения найти не удалось. В результате была написана небольшая NIF библиотека для решения этой задачи. NIF (Native Implemented Function) — это функция, написанная на Си, которую можно вызывать из Erlang’а. Эта функция должна находится в динамической библиотеке, которая, соответственно, называется NIF-библиотекой (NIF library).
Как выяснилось, пишутся NIF’ки довольно просто. Заводим новый модуль:
-export([get/0]).
-on_load(init/0).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
init() ->
case code:which(load_average) of
Filename when is_list(Filename) ->
erlang:load_nif(filename:join([filename:dirname(Filename),
"..","priv",
"load_average"]), []);
Reason when is_atom(Reason) ->
{error, Reason}
end.
-spec get() -> {ok, {float(),float(),float()}} | { error, atom() }.
% @doc Returns system load average
get() ->
erlang:nif_error(nif_library_not_loaded).
-ifdef(TEST).
get_test() ->
{ok,{X,Y,Z}} = ?MODULE:get(),
true = is_float(X),
true = is_float(Y),
true = is_float(Z).
-endif.
Главное здесь — это вызов функции erlang:load_nif/2
. Первым аргументом ей должен быть передан путь к NIF-библиотеке. Путь этот может немного меняться в зависимости от всякого, поэтому используется прием с определением пути к beam-файлу текущего модуля.
Код NIF-библиотеки размещаем в c_src/load_average.c:
static ERL_NIF_TERM get(
ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
double load_avg[3];
int loads;
loads = getloadavg(load_avg, 3);
if(loads != 3) {
return enif_make_tuple(
env, 2,
enif_make_atom(env, "error"),
enif_make_atom(env, "invalid_result")
);
}
return enif_make_tuple(
env, 2,
enif_make_atom(env, "ok"),
enif_make_tuple(
env, 3,
enif_make_double(env, load_avg[0]),
enif_make_double(env, load_avg[1]),
enif_make_double(env, load_avg[2])
)
);
}
static ErlNifFunc nif_funcs[] = {
{"get", 0, get}
};
ERL_NIF_INIT(load_average, nif_funcs, NULL, NULL, NULL, NULL)
Как видите, вся работа сводится к преобразованию сишных типов в Erlang’овские, а также в вызове функции getloadavg
. При написании этого кода мне здорово помог вот этот мануал.
Финальным аккордом дописываем в rebar.config:
{"priv/load_average.so", ["c_src/load_average.c"]}
]}.
Компилируем:
… и NIF-библиотека готова! Полную версию исходного кода вы найдете в этом архиве. В целом от открытия мануала до получения готовой библиотеки у меня ушло менее часа. Вообще, в Erlang’е все очень просто и логично устроено, и ко всему есть хорошая документация. Меня, как новичка в данном языке, это ужасно радует.
Как заверил меня более опытный коллега, NIF’ы — они очень быстрые, но когда падают, тянут за собой всю виртуальную машину Erlang’а. И еще они как бы не очень хорошо переносимы. И есть у меня подозрения, что с многопоточностью там не все просто. В данном конкретном случае, например, вместо написания NIF можно было вызвать утилиту uptime с помощью os:cmd/1
и пропарсить ее вывод. Тут есть свои минусы, но в целом подход нормальный.
Дополнение: Особую опасность представляют собой NIF, которые выполняются долго (условно, больше 1-2 мс). Такие NIF останавливают выполнение всех акторов, живущих с ними на одном треде. Возможные решения этой проблемы описаны в секции Long-running NIFs документации к библиотеке erl_nif.
Дополнение: См также мою заметку о трассировке в Erlang.
Метки: C/C++, Erlang, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.