如何优化Erlang中数千条消息的接收循环?
在编程Erlang一书的“编程多核CPU”一章中,Joe Armstrong给出了一个映射函数并行化的好例子:如何优化Erlang中数千条消息的接收循环?,erlang,parallel-processing,Erlang,Parallel Processing,在编程Erlang一书的“编程多核CPU”一章中,Joe Armstrong给出了一个映射函数并行化的好例子: pmap(F, L) -> S = self(), %% make_ref() returns a unique reference %% we'll match on this later Ref = erlang:make_ref(), Pids = map(fun(I) -> spawn(fun() ->
pmap(F, L) ->
S = self(),
%% make_ref() returns a unique reference
%% we'll match on this later
Ref = erlang:make_ref(),
Pids = map(fun(I) ->
spawn(fun() -> do_f(S, Ref, F, I) end)
end, L),
%% gather the results
gather(Pids, Ref).
do_f(Parent, Ref, F, I) ->
Parent ! {self(), Ref, (catch F(I))}.
gather([Pid|T], Ref) ->
receive
{Pid, Ref, Ret} -> [Ret|gather(T, Ref)]
end;
gather([], _) ->
[].
它工作得很好,但我相信它有一个瓶颈,导致它在包含100000多个元素的列表上工作得非常慢
执行gather()
函数时,它开始将Pid
列表中的第一个Pid
与主进程邮箱中的消息相匹配。但是如果邮箱中最早的邮件不是来自这个Pid
?然后它尝试所有其他消息,直到找到匹配的消息。也就是说,在执行gather()
函数时,我们有一定的可能性必须遍历所有邮箱邮件,以找到与从Pid
列表中获取的Pid
匹配的邮件。对于大小为N的列表,这是N*N的最坏情况
我甚至成功地证明了这个瓶颈的存在:
gather([Pid|T], Ref) ->
receive
{Pid, Ref, Ret} -> [Ret|gather(T, Ref)];
%% Here it is:
Other -> io:format("The oldest message in the mailbox (~w) did not match with Pid ~w~n", [Other,Pid])
end;
如何避免此瓶颈?在这种情况下,您可以使用(从衍生流程的pid到原始列表中的索引)作为
pid
。问题是,如果您想要获得正确的解决方案,您仍然必须:
- 检查给定的答复是否来自您拥有的某个进程 产生
- 确保正确的结果顺序
Ref
的匹配确保我们收到的信息来自我们的孩子。通过在pmap
末尾使用列表:keysort/2
对结果进行排序来确保正确的顺序,这增加了一些开销,但可能小于O(n^2)
Joe的例子很好,但实际上你需要一个更重要的解决方案来解决你的问题。举个例子来看 一般来说,您需要做三件事:
dict
按建议存储这些内容。对于大型元素列表,它的速度更快基本规则是,只要您想要并行,就很少需要基于列表的数据表示。列表具有内在的线性,这是您不想要的。盖伊·斯蒂尔(Guy Steele)就这一主题进行了一次演讲:它使用了
列表:foldl
,而不是地图
,您可能还没有实现这些列表。看看人列表
或书中的定义/实现(我相信它在那里)。你正在链接到sets手册,但文本说它是dict。应该是哪个?问题是将每个答案与初始列表中的参数连接起来。如果您使用dict
,则更容易做到这一点。否则,将更难获得正确的订单。@gleber:已修复。我最初有集合,然后意识到你需要保留索引。这似乎是一个非常简单的问题,令人惊讶的是仍然没有很好的答案。
-module(test).
-compile(export_all).
pmap(F, L) ->
S = self(),
% make_ref() returns a unique reference
% we'll match on this later
Ref = erlang:make_ref(),
Count = lists:foldl(fun(I, C) ->
spawn(fun() ->
do_f(C, S, Ref, F, I)
end),
C+1
end, 0, L),
% gather the results
Res = gather(0, Count, Ref),
% reorder the results
element(2, lists:unzip(lists:keysort(1, Res))).
do_f(C, Parent, Ref, F, I) ->
Parent ! {C, Ref, (catch F(I))}.
gather(C, C, _) ->
[];
gather(C, Count, Ref) ->
receive
{C, Ref, Ret} -> [{C, Ret}|gather(C+1, Count, Ref)]
end.