查看源代码 函数

模式匹配

函数头中的模式匹配,以及 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 的子句。由于变量可以匹配任何内容,包括原子 fourfivesix,而后续子句也匹配这些原子,因此编译器必须生成次优的代码,其执行方式如下:

  • 首先,将输入值与 onetwothree 进行比较(使用单条指令进行二分查找;因此,即使有许多值,效率也很高),以选择要执行的前三个子句中的哪一个(如果有)。
  • 如果前三个子句都不匹配,则第四个子句匹配,因为变量总是匹配的。
  • 如果守卫测试 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.

对于输入列表非空或非常短的情况(可能也是最常见的情况),这稍微快一些。(另一个优点是 Dialyzer 可以推断出 Xs 变量的更好类型。)

函数调用

以下是不同类型的函数调用性能的大致层次结构

  • 调用本地或外部函数(foo(), m:foo())是最快的调用。
  • 调用或应用一个 fun(Fun(), apply(Fun, []))只比外部调用稍微慢一点。
  • 接下来是应用一个导出的函数(Mod:Name(), apply(Mod, Name, [])),其中参数的数量在编译时已知。
  • 应用一个导出的函数(apply(Mod, Name, Args)),其中参数的数量在编译时未知,效率最低。

注释和实现细节

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

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

将回调函数缓存到 fun 中,对于频繁使用的回调,从长远来看可能比 apply 调用更有效。