非常教程

Erlang 20参考手册

指南:效率指南 | Guide: Efficiency guide

6.函数 | 6. Functions

6.1模式匹配

编译器对函数头以及in casereceive子句中的模式匹配进行了优化。除少数例外情况外,重新安排条款没有任何好处。

二进制文件的模式匹配是一个例外。编译器不会重新排列与二进制文件匹配的子句。放置与最后一个空的二进制文件匹配的子句通常比放置第一个要快一些。

以下是一个相当不自然的例子来显示另一个例外:

不要

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。由于变量可以匹配任何东西,包括原子fourfive以及six以下子句也匹配,编译器必须生成次优代码,其执行如下:

  • 首先,输入值与onetwo,和three(使用单个指令,做了二分搜索,因此,即使有许多值非常有效),以选择前三个子句之一执行哪个(如果有的话)。
  • 如果没有前三个子句匹配,则第四个子句匹配作为变量始终匹配。
  • 如果保护测试is_integer(Int)成功,则执行第四个条款。
  • 如果保护测试失败,以进行比较的输入值fourfivesix,并且选择适当的子句。(function_clause如果没有任何值匹配,则会出现异常。)

重写为:

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.

或:

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.

提供稍微更有效的匹配代码。

另一个例子:

不要

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,在中间子句中。由于变量可以匹配任何内容,因此编译器不允许重新排列子句,但必须生成与写入顺序相匹配的代码。

如果函数被重写如下,编译器可以自由地重新排列子句:

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)].

编译器将生成类似如下的代码:

不要(已经由编译器完成)

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.

这可能是最常见的情况,输入列表不是空的或非常短的。(另一个优点是透析器可以为Xs变量推导出更好的类型。)

6.2函数调用

这是针对不同呼叫的相对成本的故意粗略指南。它基于在Solaris / Sparc上运行的基准测试数据:

  • 本地调用或外部函数(foo()m:foo())是最快的调用。
  • 调用或应用趣味(Fun()apply(Fun, []))大约是调用本地函数的三倍
  • 应用导出的函数(Mod:Name()apply(Mod, Name, []))大约是调用fun的两倍,或者大约是调用本地函数的六倍

注释和实现细节

调用和应用趣味不涉及任何散列表查找。fun包含一个指向实现fun的函数的(间接)指针。

apply/3必须查找要在哈希表中执行的函数的代码。因此,它总是比直接电话或有趣的电话慢。

(从性能角度来看)你是否写下了以下内容:

Module:Function(Arg1, Arg2)

或:

apply(Module, Function, [Arg1,Arg2])

编译器在内部将后一段代码重写为前者。

下面的代码稍微慢一些,因为参数列表的形状在编译时是未知的。

apply(Module, Function, Arguments)

6.3 递归中的内存使用

在编写递归函数时,最好使它们成为尾递归的,以便它们可以在常量内存空间中执行:

list_length(List) ->
    list_length(List, 0).

list_length([], AccLen) -> 
    AccLen; % Base case

list_length([_|Tail], AccLen) ->
    list_length(Tail, AccLen + 1). % Tail-recursive

不要

list_length([]) ->
    0. % Base case
list_length([_ | Tail]) ->
    list_length(Tail) + 1. % Not tail-recursive
Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1