• [Erlang0007][OTP] 高效指南 函数


    6 函数

    6.1 模式匹配


    函数头以及case和receive子句中的模式匹配都被编译器优化过。除了个别例外,大部分情况下调整顺序不会带来任何好处。

    二进制就是一个例外。匹配二进制时,编译器不会重新排列分支。把一个空的二进制放在后边通常会比放在前面稍快。

    下面的例子展示了另一种例外情况:

    DO NOT
    atom_map1(one) -> 1;
    atom_map1(two) -> 2;
    atom_map1(three) -> 3;
    atom_map1(Int) when is_integer(Int) -> Int;
    atom_map1(four) -> 4;
    atom_map1(five) -> 5;
    atom_map1(six) -> 6.

    这里的问题在于分支中有一个变量Int。因为变量可以匹配任意东西,包括它下面的分支原子four,five,six,经编译器优化后的代码,执行如下:

    首先输入的参数会和one,two,three(用一个简单的二分查找;即使有许多分支也很高效)进行比较,以找到前三个分支中匹配的一个,并且执行(如果有的话)。

    如果前三个都不匹配,第四个分支就会匹配,因为变量可以配位一切。如果断言测试is_integer(Int)成功,第四个分支就会被执行。

    如果断言测试失败,输入值会和four,five,six比较,适当的分支会被选中。(如果都不匹配就会抛出function_clause的异常。)

    可以重写为下面两种:

    DO
    atom_map2(one) -> 1;
    atom_map2(two) -> 2;
    atom_map2(three) -> 3;
    atom_map2(four) -> 4;
    atom_map2(five) -> 5;
    atom_map2(six) -> 6;
    atom_map2(Int) when is_integer(Int) -> Int.

    or

    DO
    atom_map3(Int) when is_integer(Int) -> Int;
    atom_map3(one) -> 1;
    atom_map3(two) -> 2;
    atom_map3(three) -> 3;
    atom_map3(four) -> 4;
    atom_map3(five) -> 5;
    atom_map3(six) -> 6.

    这样会使匹配代码更高效些。

    这里有一个不太恰当的例子:

    DO NOT
    
    map_pairs1(_Map, [], Ys) ->
      Ys;
    map_pairs1(_Map, Xs, [] ) ->
      Xs;
    map_pairs1(Map, [X|Xs], [Y|Ys]) ->
      [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

    第一个参数不是问题。它是一个变量,而且在所有分支都是变量。问题是第二个参数是个变量,Xs,在中间一个分支。以为变量能匹配一切,编译器不会自动重排分支,而必须按照顺序生成代码。

    如果这个函数像这样重写:

    DO
    
    map_pairs2(_Map, [], Ys) ->
      Ys;
    map_pairs2(_Map, [_|_]=Xs, [] ) ->
      Xs;
    map_pairs2(Map, [X|Xs], [Y|Ys]) ->
      [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

    编译器就可以随意的重排分支,会生成像这样的代码:

    DO NOT (already done by the compiler)
    
    explicit_map_pairs(Map, Xs0, Ys0) ->
        case Xs0 of
            [X|Xs] ->
                case Ys0 of
                    [Y|Ys] ->
                        [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
                    [] ->
                        Xs0
                end;
            [] ->
                Ys0
        end.                        

    这样会更快一些,尤其是长列表的时候。(这样做的另一个优点是Dialyzer能够更好的分析出变量Xs的类型。)

    6.2 函数调用

    这里有一个不同类型的函数调用的相对代价的粗略指引。基准数字是在Solaris/Sparc上得出的:

    1. 本地调用和外部调用(foo(),m:foo())是最快的。
    2. 调用fun函数(Fun(),apply(Fun, []))代价大约会比本地调用大三倍。
    3. 调用一个导出函数(Mod:Name(),Apply(Mod,Name,[]))代价两倍于调用fun函数,六倍于调用本地函数。

    注意事项和实现细节

    调用fun函数不需要查询哈希表。一个fun函数包含只想函数具体实现的指针。

    注意:
    元组不是fun函数,一个“tuple fun”,{Module,Function},不是fun函数。调用“tuple fun”的代价和apply/3差不多,甚至跟糟糕。极其不推荐使用“tuple funs”,而且有可能在今后的发布版本中这种方式将不会被支持,因为自从R10B开始就有一个超级替代品,叫做fun Module:Function/Arity语法。

    apply/3必须通过一个哈希表查询代码找到函数再执行。因此,比直接调用或fun要慢。

    你不需要再关心如何这样写(从性能的角度出发)


    Module:Function(Arg1, Arg2)


    还是


    apply(Module, Function, [Arg1,Arg2])
    (编译器会把下面一种写法优化成上面那种)

    下面的代码
    apply(Module, Function, Arguments)
    会稍微慢一点点,因为编译器不知道参数的个数。

    6.3 递归的内存使用

    当写递归函数的时候最好写成尾递归,以便执行的时候只使用固定大小的内存空间。

    DO
    
    list_length(List) ->
      list_length(List, 0).
    
    list_length([], AccLen) -> 
      AccLen; % Base case
    
    list_length([_|Tail], AccLen) ->
      list_length(Tail, AccLen + 1). % Tail-recursive
    DO NOT
    
    list_length([]) ->
      0. % Base case
    list_length([_ | Tail]) ->
      list_length(Tail) + 1. % Not tail-recursive
  • 相关阅读:
    让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法
    检测到有潜在危险的 Request.Form 值
    jQuery校验
    C#客户端的异步操作
    泛型(一)
    c#面试题汇总
    多线程(下)
    多线程(上)
    线程篇(二)
    线程篇(一)
  • 原文地址:https://www.cnblogs.com/liangjingyang/p/2699290.html
Copyright © 2020-2023  润新知