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

|| ((请原谅我在一个线程中问了多个问题。我认为它们是相关的。) 您好,我想知道,关于每个模块的预编译数据,Erlang中存在哪些最佳实践。 示例:我有一个模块,该模块对先验知识,复杂的正则表达式进行大量运算。 re:compile / 2 \的文档说:“编译一次并执行多次要比每次匹配时的编译效率要高得多”。由于未指定re \的mp()数据类型,因此如果要使用目标无关的光束,则无法在编译时放置它,因此必须在运行时编译RegEx。 ((注意:re:compile / 2只是一个示例。要记住的任何复杂函数都适合我的问题。)) Erlang的模块(可以)具有
-on_load(F/A)
属性,表示加载该模块时应执行一次的方法。这样,我可以将正则表达式放在此方法中进行编译,然后将结果保存在名为
?MODULE
的新ets表中。 在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
.
    
已邀请:
        mochiglobal通过编译一个新模块来存储常量来实现这一点。这样做的好处是,内存在各个进程之间共享,在内存中它被复制,而在进程字典中它仅位于该进程的本地。 https://github.com/mochi/mochiweb/blob/master/src/mochiglobal.erl     
        您还有另一个选择。您可以预先计算正则表达式的编译形式并直接引用它。一种实现方法是使用专门为此目的设计的模块,例如ѭ3:http://dukesoferl.blogspot.com/2009/08/metaprogramming-with-ctexpand.html 您也可以通过动态生成一个模块来滚动自己的函数,该模块具有一个将该值作为常量返回的函数(利用常量池):http://erlang.org/pipermail/erlang-questions/2011-January/ 056007.html 或者甚至可以在shell中运行run4ѭ并将结果复制并粘贴到代码中。粗暴但有效。万一实现发生变化,这将是不可移植的。 需要明确的是:所有这些都利用了常量池,以避免每次都重新计算。但是,当然,这增加了复杂性,并带来了成本。 回到您最初的问题:流程字典的问题是,它只能由自己的流程使用。您确定此模块的功能只能由同一进程调用吗?甚至ETS表都与创建它们的过程相关联(不过,ETS本身并不是使用过程和消息传递来实现的),如果该过程终止,则将失效。     
ETS不在流程中实现,并且其数据不在单独的流程堆中,但是它的数据确实在所有流程之外的单独区域中。这意味着在读/写ETS表时,必须将数据复制到进程或从进程复制。当然,这有多昂贵,取决于要复制的数据量。这就是为什么我们拥有诸如
ets:match_object
ets:select
之类的功能的原因之一,这些功能允许在复制数据之前使用更复杂的选择规则。 将数据保存在ETS表中的一个好处是,所有进程都可以访问它,而不仅仅是拥有该表的进程。与将数据保存在服务器中相比,这可以使其效率更高。它还取决于您要对数据执行哪种类型的操作。 ETS只是一个数据存储,并提供有限的原子性。在您的情况下,这可能没问题。 您绝对应该将数据保存在单独的记录中,每个记录分别对应一个不同的已编译正则表达式,因为这将大大提高访问速度。然后,您可以直接获取所需的资源,否则将全部获取,然后在所需的资源之后再次搜索。那样的失败使他们无法进入ETS。 尽管您可以执行诸如在
on_load
函数中构建ETS表之类的事情,但对于ETS表却不是一个好主意。这是因为ETS属于进程所有,并且在进程终止时被删除。您永远不会真正知道在哪个过程中调用
on_load
函数。您还应避免执行可能会花费很长时间的操作,因为直到完成模块才将其视为已加载。 生成一个解析转换以静态地将编译res的结果直接插入代码中是一个很不错的主意,尤其是在您re的确是静态定义的情况下。正如动态生成,编译模块并将其加载到系统中的想法一样。同样,如果您的数据是静态的,则可以在编译时生成此模块。     

要回复问题请先登录注册