【问题标题】:Erlang pattern matching performanceErlang 模式匹配性能
【发布时间】:2011-06-13 15:58:59
【问题描述】:

Erlang 新手。我即将开始编写一些代码。我想到的解决方案可以采用两种方式之一。我可以做一堆数学计算,或者我可以将其编码为模式匹配。当我说“模式匹配”时,我不是指正则表达式或类似的东西——我指的是子句头部中的模式匹配。

性能通常不是问题,但在这个应用程序中却是。我不是在问你哪种方法会更快——我敢肯定你会说你不知道(取决于许多因素)。我要问的是 Erlang 模式匹配在子句头中的一般性能。换句话说,在 Prolog 中,引擎被优化来做这种事情,所以在所有其他条件相同的情况下,你被“鼓励”设计一个利用模式匹配和子句统一的解决方案。

Erlang 是否也是如此,即 Erlang 是否针对子句头部的模式匹配进行了优化,类似于 Prolog?我实际上并没有在这里问这个问题,而是尝试在 Erlang 中对此进行分析,并编写了一个玩具程序来进行几百万次子句头部的模式匹配,而不是对一个列表求和几百万次。但是如果设置为“几百万次”,系统就会崩溃。但是如果设置为少于几百万,结果会返回得太快,以至于我对性能一无所知。

感谢您提供任何见解。

【问题讨论】:

  • 发布 Codz。改写为一个关于为什么会崩溃的问题。
  • 代码是 2 个非常简单的程序。第一个程序是 10 个子句头,序列子句 (0) -> 0;子句(1)-> 0; (最多 10 个)为了测试,我使用 duplicate 创建了一个包含 200 万个 0 的列表,并使用 map to map 子句到它上面。第二个程序只是简单地使用 duplicate 创建 200 万个包含 7 个整数的列表,然后使用 map 将 sum 映射到它上面。我再次尝试测试用作编程技术的模式匹配的相对性能。我的问题是是否鼓励 Erlang 程序员像 Prolog 程序员一样使用模式匹配(在可行的情况下)。

标签: performance erlang pattern-matching


【解决方案1】:

基本上就像@(不是很) CRAP ANSWERS :-) 所说的那样,他的例子表明了。

Prolog 并没有真正做单向的模式匹配,它做了 unification 这有点像一个带有逻辑变量的双向模式匹配。优化模式匹配要容易得多,而 Erlang 和 Haskell 等严肃的函数式语言在优化模式匹配编译器中投入了大量工作。这在深度模式中尤其明显。

所以,是的,Erlang 会比 Prolog 更快地进行模式匹配。

【讨论】:

    【解决方案2】:

    一般来说,函数式语言中的模式匹配与 Prolog 中的一样快或更快。与 Prolog 相比,我希望 Erlang 的性能在 2 倍以内,更快或更慢。由于函数式程序几乎只是模式匹配,因此它是您经常优化的领域之一。

    内部通常有一个模式匹配编译器,它将高级模式匹配转换为更简单的一系列检查,目标是最大限度地减少检查次数。


    好的,第一个问题:“shell 中引入的任何东西都会被解释”。所以我们编译模块:

    -module(z).
    
    -compile(export_all).
    
    %% This pattern is highly uninteresting because it only matches
    %% on a single value. Any decent system can do this quickly.
    cl(0) -> 0;
    cl(1) -> 0;
    cl(2) -> 0;
    cl(3) -> 0;
    cl(4) -> 0;
    cl(5) -> 0;
    cl(6) -> 0;
    cl(7) -> 0;
    cl(8) -> 0;
    cl(9) -> 0.
    
    mcl(L) ->
        [cl(E) || E <- L].
    

    这给了我们一个你的例子的跑步者。

    cl2(a, 0) -> a0;
    cl2(a, 1) -> a1;
    cl2(a, 2) -> a2;
    cl2(b, 0) -> b0;
    cl2(b, 1) -> b1;
    cl2(b, 2) -> b2;
    cl2(c, 0) -> c0;
    cl2(c, 1) -> c0;
    cl2(c, 2) -> c0.
    
    mcl2(L) ->
        [cl2(A, V) || {A, V} <- L].
    

    一个更有趣的例子的跑步者。在这里,我们可以利用模式中的skips。如果(a, 0)a 上匹配失败,我们知道我们可以直接跳到(b, 0) 的情况,因为否定匹配信息可以作为系统中的信息传播。模式匹配编译器通常会进行这种优化。

    test1() ->
        L1 = [X rem 10 || X <- lists:seq(1, 2000000)],
        %% A Core 2 Duo 2.4Ghz P8600 eats this in 132984 microseconds without HiPE
        %% c(z).
        %% With HiPE it is 91195 or in 0.6857591890753775 of the time the non-HiPE variant use
        %% c(z, [native, {hipe, [o3]}]).
        timer:tc(z, mcl, [L1]).
    

    您必须自己运行此示例并评估您是否认为它对于您的用例来说足够快。请注意,映射代码也花费了一些时间,并且必须花费大量时间将数据从主内存通过 CPU 缓存拉到 CPU 上。

    test2() ->
        random:seed(erlang:now()),
        L2 = [{case random:uniform(3) of
                       1 -> a;
                       2 -> b;
                       3 -> c
                       end, V rem 3} || V <- lists:seq(1, 2000000)],
        %% With HiPE this is 220937
        %% Without HiPE this is 296980
        timer:tc(z, mcl2, [L2]).
    

    这个例子自然会比较慢,因为我们需要匹配更多的数据才能命中。但这是一个更有趣的案例,因为它给出了匹配编译器真实速度的一些指示。


    尝试了并行版本,但在这种情况下它们慢了大约 10 倍,因为创建 200 万个工作进程的开销远远超过了实际处理 :)

    【讨论】:

    • 会切换到静态类型检查语言,例如Haskell,一起消除 OP 的顾虑吗?
    • 感谢您的评论...不,我不会切换到静态类型语言 :-)
    • 静态语言通常有更快的模式匹配,因为它们有更多的信息可以利用来对可能到达的数据进行类型化的模式匹配。然而,在 Erlang 中使用 HiPE 编译可以让您恢复一些。
    • 出色的分析——是的,这就是我想做的那种实验。
    猜你喜欢
    • 2014-06-27
    • 2011-06-30
    • 2018-12-08
    • 2011-08-14
    • 2011-06-17
    • 2013-08-15
    • 2011-09-29
    • 2010-12-13
    • 2014-07-26
    相关资源
    最近更新 更多