檢視原始碼 函數

模式比對

函數頭以及 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 呼叫在長期而言更有效率。