Ускорение и лучшие практики: использование ets для предварительно рассчитанных данных на модуль - PullRequest
9 голосов
/ 31 мая 2011

((Пожалуйста, прости меня, что я задаю более одного вопроса в одной теме. Я думаю, что они связаны.))

Здравствуйте, я хотел знать, какие лучшие практики существуют в Erlang в отношении предварительно скомпилированных данных для каждого модуля.

Пример: У меня есть модуль , который интенсивно работает на априорном, очень сложном регулярных выражениях. re: compile / 2 документация гласит: «Компиляция один раз и многократное выполнение гораздо эффективнее, чем компиляция каждый раз, когда нужно найти соответствие». Поскольку тип данных re (mp ()) никоим образом не указан, и поэтому его нельзя поместить во время компиляции, если вам нужен независимый от цели луч, необходимо скомпилировать RegEx во время выполнения. (( Примечание: re: compile / 2 - только пример. Любая сложная функция для запоминания подойдет моему вопросу.))

Модуль Эрланга (может) имеет атрибут -on_load(F/A), обозначающий метод, который должен выполняться один раз при загрузке модуля . Таким образом, я мог бы разместить свои регулярные выражения для компиляции в этом методе и сохранить результат в новой ets таблице с именем ?MODULE.

Обновлено после ответа Дэна.

Мои вопросы:

  • Если я правильно понимаю ets, его данные сохраняются в другом процессе (иначе образуют словарь процессов), и получение значения для таблицы ets довольно дорого. (Пожалуйста, докажите, что я не прав, если я ошибаюсь!) Следует ли скопировать содержимое ets в словарь процессов для ускорения? (Помните: данные никогда не обновляются.)
  • Существуют ли (значительные) недостатки размещения всех данных как одной записи (вместо множества элементов таблицы) в словаре ets / process?

Рабочий пример:

-module(memoization).
-export([is_ipv4/1, fillCacheLoop/0]).
-record(?MODULE, { re_ipv4 = re_ipv4() }).
-on_load(fillCache/0).

fillCacheLoop() ->
    receive
        { replace, NewData, Callback, Ref } ->
            true = ets:insert(?MODULE, [{ data, {self(), NewData} }]),
            Callback ! { on_load, Ref, ok },
            ?MODULE:fillCacheLoop();
        purge ->
            ok
    end
.
fillCache() ->
    Callback = self(),
    Ref = make_ref(),
    process_flag(trap_exit, true),
    Pid = spawn_link(fun() ->
        case catch ets:lookup(?MODULE, data) of
            [{data, {TableOwner,_} }] ->
                TableOwner ! { replace, #?MODULE{}, self(), Ref },
                receive
                    { on_load, Ref, Result } ->
                        Callback ! { on_load, Ref, Result }
                end,
                ok;
            _ ->
                ?MODULE = ets:new(?MODULE, [named_table, {read_concurrency,true}]),
                true = ets:insert_new(?MODULE, [{ data, {self(), #?MODULE{}} }]),
                Callback ! { on_load, Ref, ok },
                fillCacheLoop()
        end
    end),
    receive
        { on_load, Ref, Result } ->
            unlink(Pid),
            Result;
        { 'EXIT', Pid, Result } ->
            Result
    after 1000 ->
        error
    end
.

is_ipv4(Addr) ->
    Data = case get(?MODULE.data) of
        undefined ->
            [{data, {_,Result} }] = ets:lookup(?MODULE, data),
            put(?MODULE.data, Result),
            Result;
        SomeDatum -> SomeDatum
    end,
    re:run(Addr, Data#?MODULE.re_ipv4)
.

re_ipv4() ->
    {ok, Result} = re:compile("^0*"
            "([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*"
            "([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*"
            "([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*"
            "([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])$"),
    Result
.

Ответы [ 3 ]

7 голосов
/ 31 мая 2011

У вас есть другой вариант.Вы можете предварительно вычислить скомпилированную форму регулярного выражения и обратиться к ней напрямую.Один из способов сделать это - использовать модуль, разработанный специально для этой цели, такой как ct_expand: http://dukesoferl.blogspot.com/2009/08/metaprogramming-with-ctexpand.html

. Вы также можете свернуть свой собственный, сгенерировав модуль на лету с функцией, возвращающей это значение.как константа (используя константный пул): http://erlang.org/pipermail/erlang-questions/2011-January/056007.html

Или вы можете даже запустить re:compile в оболочке, скопировать и вставить результат в свой код.Грубо, но эффективно. Это не будет переносимым в случае изменения реализации.

Для ясности: все они используют постоянный пул, чтобы избежать повторного вычисления каждый раз.Но, конечно, это добавляет сложности и требует затрат.

Возвращаясь к исходному вопросу: проблема со словарем процессов заключается в том, что он может использоваться только собственным процессом.Вы уверены, что функции этого модуля будут вызываться только одним и тем же процессом?Даже таблицы ETS связаны с процессом, который их создает (хотя ETS не сам реализован с использованием процессов и передачи сообщений) и умрет, если этот процесс умрет.

5 голосов
/ 01 июня 2011

ETS не реализован в процессе и не имеет своих данных в отдельной куче процесса, но он имеет , что его данные находятся в отдельной области вне всех процессов.Это означает, что при чтении / записи в таблицы ETS данные должны копироваться в / из процессов.Насколько это дорого, зависит, конечно, от объема копируемых данных.Это одна из причин, почему у нас есть такие функции, как ets:match_object и ets:select, которые позволяют более сложные правила выбора до копирования данных.

Одним из преимуществ хранения ваших данных в таблице ETS являетсячто это может быть достигнуто всеми процессами, а не только процессом, которому принадлежит таблица.Это может сделать его более эффективным, чем хранить ваши данные на сервере.Это также зависит от того, какой тип операций вы хотите выполнить с вашими данными.ETS - это просто хранилище данных и обеспечивает ограниченную атомарность.В вашем случае это, вероятно, не проблема.

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

Хотя вы можете делать такие вещи, как создание таблиц ETS в функциях on_load, для таблиц ETS не очень хорошая идея.Это потому, что ETS принадлежит процессу и удаляется, когда процесс умирает.Вы никогда не знаете, в каком процессе вызывается функция on_load.Вам также следует избегать действий, которые могут занять много времени, поскольку модуль не считается загруженным до его завершения.

Создание преобразования синтаксического анализа для статической вставки результата компиляции ваших файлов непосредственно в ваш кодклассная идея, особенно если вы действительно статически определены.Как и идея динамического создания, компиляции и загрузки модуля в вашу систему.Опять же, если ваши данные настолько статичны, вы можете сгенерировать этот модуль во время компиляции.

3 голосов
/ 31 мая 2011

mochiglobal реализует это путем компиляции нового модуля для хранения ваших констант (ы). Преимущество здесь в том, что память распределяется между процессами, где в ets она копируется, а в словаре процессов она просто локальна для этого процесса.

https://github.com/mochi/mochiweb/blob/master/src/mochiglobal.erl

...