如何优化Erlang中数千条消息的接收循环?

如何优化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() ->

在编程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() -> 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的例子很好,但实际上你需要一个更重要的解决方案来解决你的问题。举个例子来看

一般来说,您需要做三件事:

  • 选择一个“足够大”的工作单元。如果工作单元太小,则会因处理开销而死亡。如果它太大,您将因工人闲置而死亡,特别是如果您的工作未按列表中的元素计数平均分配

  • 上限同时工作的数量。Psyeugenic建议按调度器进行拆分,我建议按作业计数限制进行拆分,比如说100个作业。也就是说,你想要开始100个工作,然后等到其中一些工作完成后再开始更多的工作

  • 如果可能,考虑按元件顺序拧紧。如果你不需要考虑订单的话,速度会快得多。对于许多问题,这是可能的。如果顺序确实重要,那么使用
    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.