如何解决加速和最佳实践:将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,Result } ->
Callback ! { on_load,Result }
end,ok;
_ ->
?MODULE = ets:new(?MODULE,[named_table,{read_concurrency,true}]),true = ets:insert_new(?MODULE,#?MODULE{}} }]),fillCacheLoop()
end
end),receive
{ on_load,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的确是静态定义的情况下。正如动态生成,编译模块并将其加载到系统中的想法一样。同样,如果您的数据是静态的,则可以在编译时生成此模块。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。