[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
原文地址:https://www.cnblogs.com/liangjingyang/p/2699290.html