檢視原始碼 函數
模式比對
函數頭以及 case
和 receive
子句中的模式比對會由編譯器進行最佳化。除了少數例外,重新排列子句並不會帶來任何好處。
其中一個例外是二元模式的比對。編譯器不會重新排列比對二元的子句。將比對空二元的子句放在最後通常會比放在最前面稍微快一些。
以下是一個相當不自然的例子,用來說明重新排列子句會有好處的另一個例外情況
不要這樣做
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
例外。)
改寫成以下任一方式
這樣做
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 呼叫在長期而言更有效率。