加速和最佳实践:为每个模块预先计算的数据使用ets

kay*_*kay 9 erlang big-o memoization ets

((请原谅我,我在一个帖子中提出了不止一个问题.我认为它们是相关的.))

您好,我想知道,Erlang中有关每模块预编译数据的最佳实践.

示例:我有一个模块,它在一个熟悉的知识,复杂的正则表达式上运行.re:compile/2的文档说:"编译一次并执行多次比每次想要匹配时编译效率要高得多".由于没有指定re的mp()数据类型,因此如果你想要一个目标独立的梁,就不能在编译时放入,因此必须在运行时编译RegEx.((注意: re:compile/2只是一个例子.任何复杂的memoize函数都适合我的问题.))

Erlang的模块(可以)有一个-on_load(F/A)属性,表示在加载模块时应该执行一次的方法.因此,我可以将我的正则表达式编译为此方法并将结果保存在名为的新ets表中?MODULE.

Dan回答后更新.

我的问题是:

  • 如果我正确理解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
.
Run Code Online (Sandbox Code Playgroud)

Dan*_*Dan 7

你有另一种选择.您可以预先计算正则表达式的编译表单并直接引用它.一种方法是使用专门为此目的设计的模块,例如ct_expand:http://dukesoferl.blogspot.com/2009/08/metaprogramming-with-ctexpand.html

您也可以通过动态生成模块来自行创建一个模块,并将该值作为常量返回(利用常量池):http://erlang.org/pipermail/erlang-questions/2011-January/ 056007.html

或者你甚至可以re:compile在shell中运行并将结果复制并粘贴到代码中.原油但有效.如果实现发生变化,这将不可移植.

需要明确的是:所有这些都利用了常量池来避免每次重新计算.但是,当然,这增加了复杂性并且有成本.

回到你原来的问题:流程字典的问题是,它只能由它自己的进程使用.你确定这个模块的功能只能由同一个进程调用吗?甚至ETS表也与创建它们的进程相关联(尽管ETS 本身并不使用进程和消息传递实现),并且如果该进程死亡将会死亡.


rvi*_*ing 5

ETS不是在进程中实现的,并且没有将数据放在单独的进程堆中,但它确实将数据放在所有进程之外的单独区域中.这意味着在读取/写入ETS表时,必须将数据复制到进程或从进程复制.当然,这取决于复制的数据量.这就是为什么我们复制数据之前具有类似功能ets:match_objectets:select允许更复杂的选择规则的原因之一.

将数据保存在ETS表中的一个好处是,所有进程都可以访问它,而不仅仅是拥有该表的进程.这可以使其比将数据保存在服务器中更有效.它还取决于您要对数据执行的操作类型.ETS只是一个数据存储,并提供有限的原子性.在你的情况下,这可能没有问题.

您绝对应该将数据保存在单独的记录中,每个记录对应一个不同的编译正则表达式,因为它将大大提高访问速度.然后你可以直接得到你所追求的,然后你将得到它们,然后在你想要的之后再次搜索.这种做法无法将它们置于ETS中.

虽然你可以在on_load函数中构建ETS表,但对于ETS表来说这不是一个好主意.这是因为ETS由进程拥有,并在进程终止时被删除.你永远不知道on_load调用函数的过程.您还应该避免做一些可能需要很长时间的事情,因为模块在完成之前不被认为是加载的.

生成一个解析变换以静态地将你的re的编译结果直接插入到你的代码中是一个很酷的主意,特别是如果你的re真的是那个静态定义的.这是将模块动态生成,编译和加载到系统中的想法.如果您的数据是静态的,您可以在编译时生成此模块.


YOU*_*LID 3

mochiglobal 通过编译一个新模块来存储常量来实现这一点。这里的优点是内存是跨进程共享的,在 ets 中它被复制,而在进程字典中它只是该进程的本地内存。

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