能夠經過如下方式有效地構建二進制:ide
my_list_to_binary(List) ->函數
my_list_to_binary(List, <<>>).優化
my_list_to_binary([H|T], Acc) ->ui
my_list_to_binary(T, <<Acc/binary,H>>);spa
my_list_to_binary([], Acc) ->指針
Acc.對象
二進制能夠像這樣有效地匹配:進程
my_binary_to_list(<<H,T/binary>>) ->ci
[H|my_binary_to_list(T)];編譯器
my_binary_to_list(<<>>) -> [].
在內部,二進制和位串以相同的方式實現。在本節中,它們被稱爲二進制,由於這就是它們在模擬器源代碼中的名稱。
內部有四種類型的二進制對象:
Refc二進制包含兩個部分:
二進制對象可由任意數量的進程中的任意數量的ProcBins引用。該對象包含一個引用計數器,以跟蹤引用的數量,以便在最後一個引用消失時能夠將其刪除。
進程中的全部ProcBin對象都是連接列表的一部分,所以,當ProcBin消失時,垃圾收集器能夠跟蹤它們並減小二進制中的引用計數器。
堆二進制是小型二進制,最多64個字節,並直接存儲在進程堆中。當進程被垃圾回收而且做爲消息發送時,它們被複制。它們不須要垃圾收集器進行任何特殊處理。
引用對象子二進制和匹配上下文能夠引用refc二進制或堆二進制的一部分。
子二進制經過建立split_binary/2,而且當二進制以二進制模式匹配的。子二進制是對另外一個二進制(refc或堆二進制,而不是對另外一個子二進制)的一部分的引用。所以,匹配二進制相對便宜,由於從不復制實際的二進制數據。
匹配上下文相似於子二進制,但對於二進制匹配被優化。例如,它包含一個指向二進制數據的直接指針。對於從二進制中匹配的每一個字段,匹配上下文中的位置會增長。
編譯器試圖避免生成用於建立子二進制的代碼,而只是在不久以後建立一個新的匹配上下文並丟棄該子二進制。保留匹配上下文,而不是建立子二進制。
若是編譯器知道不會共享匹配上下文,則只能進行此優化。若是將其共享,則Erlang的功能屬性(也稱爲參照透明性)將中斷。
運行時系統特別優化了附加到二進制或位串的操做:
<<Binary/binary, ...>>
<<Binary/bitstring, ...>>
當運行時系統處理優化(而不是編譯器)時,在極少數狀況下優化不起做用。
爲了解釋它是如何工做的,讓咱們逐行檢查如下代碼:
Bin0 = <<0>>, %% 1
Bin1 = <<Bin0/binary,1,2,3>>, %% 2
Bin2 = <<Bin1/binary,4,5,6>>, %% 3
Bin3 = <<Bin2/binary,7,8,9>>, %% 4
Bin4 = <<Bin1/binary,17>>, %% 5 !!!
{Bin4,Bin3} %% 6
運行時系統會發現Bin1是上一個追加操做(不是最新的追加操做)的結果,所以它將Bin1的內容複製到新的二進制中,保留了額外的存儲空間,依此類推。(這裏沒有解釋運行時系統如何知道不容許將其寫入Bin1;好奇的讀者能夠將其做爲練習,經過讀取仿真器源代碼(主要是erl_bits.c)來了解如何完成此操做。)
二進制追加操做的優化要求,有一個單一ProcBin和一個單一的引用到ProcBin用於二進制。緣由是能夠在追加操做期間移動(從新分配)二進制對象,而且在這種狀況下,必須更新ProcBin中的指針。若是將有多個ProcBin指向二進制對象,則將不可能找到並更新全部它們。
所以,對二進制的某些操做會對其進行標記,以便未來任何附加操做都將被強制複製二進制。在大多數狀況下,二進制對象將同時縮小以回收分配給增加的額外空間。
當按以下所示追加到二進制時,僅從最新的追加操做返回的二進制將支持進一步的廉價追加操做:
Bin = <<Bin0,...>>
在本節開頭的代碼片斷中,追加到Bin將很便宜,而追加到Bin0將強制建立新的二進制並複製Bin0的內容。
若是將二進制做爲消息發送到進程或端口,則該二進制將縮小,而且任何進一步的追加操做會將二進制數據複製到新的二進制中。例如,在下面的代碼片斷中,Bin1將被複制到第三行:
Bin1 = <<Bin0,...>>,
PortOrPid!Bin1
Bin = <<Bin1,...>> %% Bin1將被複制
若是將二進制插入到Ets表中,或者使用erlang:port_command/2將其發送到端口,或者將其傳遞給NIF中的enif_inspect_binary,也會發生一樣的狀況。
匹配二進制也將致使其縮小,而且下一個追加操做將複製二進制數據:
Bin1 = <<Bin0,...>>,
<< X,Y,Z,T/binary>> = Bin1,
Bin = <<Bin1,...>> %% Bin1將被複制
緣由是匹配上下文包含指向二進制數據的直接指針。
若是進程僅保留二進制(在「循環數據」中或在進程字典中),則垃圾收集器最終能夠收縮二進制。若是隻保留一個這樣的二進制,它將不會縮小。若是該過程稍後追加到已縮小的二進制中,則將從新分配二進制對象以放置要附加的數據。
讓咱們回顧上一節開頭的示例:
my_binary_to_list (<< H,T/binary >>) ->
[H | my_binary_to_list(T)];
my_binary_to_list (<< >>) -> []。
首次調用my_binary_to_list/1時,將建立一個匹配上下文。匹配上下文指向二進制的第一個字節。1個字節被匹配,而且匹配上下文被更新以指向二進制中的第二個字節。
在這一點上,建立一個子二進制是有意義的,可是在此特定示例中,編譯器發現很快將調用一個函數(在本例中爲my_binary_to_list/1自己),該函數將當即建立一個新的匹配上下文並丟棄子二進制。
所以,my_binary_to_list/1會使用match上下文而不是子二進制進行調用。初始化匹配操做的指令在看到已傳遞給匹配上下文而不是二進制時,基本上什麼也不作。
當到達二進制的末尾而且第二個子句匹配時,匹配上下文將被簡單地丟棄(在下一個垃圾回收中將其刪除,由於再也不有對其的引用)。
總而言之,my_binary_to_list/1僅須要建立一個匹配上下文,而無需子二進制。
請注意,遍歷整個二進制後,將放棄my_binary_to_list/1中的match上下文。若是迭代在到達二進制末尾以前中止,會發生什麼狀況?優化是否仍然有效?
after_zero(<<0,T/binary>>) ->
T;
after_zero(<<_,T/binary>>) ->
after_zero(T);
after_zero(<<>>) ->
<<>>.
是的,它會的。編譯器將在第二個子句中刪除子二進制的構建:
...
after_zero(<<_,T/binary>>) ->
after_zero(T);
...
可是它將生成在第一個子句中構建子二進制的代碼:
after_zero(<<0,T/binary>>) ->
T;
...
所以,after_zero/1構建一個匹配上下文和一個子二進制(假定傳遞了一個包含零字節的二進制)。
以下代碼也將獲得優化:
all_but_zeroes_to_list(Buffer, Acc, 0) ->
{lists:reverse(Acc),Buffer};
all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
all_but_zeroes_to_list(T, Acc, Remaining-1);
all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).
編譯器在第二和第三子句中刪除了子二進制的構建,並向第一子句添加了一條指令,該指令將Buffer從匹配上下文轉換爲子二進制(若是Buffer已是二進制,則不執行任何操做)。
可是在更復雜的代碼中,如何知道是否應用了優化呢?
使用bin_opt_info選項可以使編譯器打印許多有關二進制優化的信息。能夠將其提供給編譯器或erlc:
erlc +bin_opt_info Mod.erl
或經過環境變量傳遞:
export ERL_COMPILER_OPTIONS=bin_opt_info
注意,bin_opt_info並非要添加到Makefile的永久選項,由於它生成的全部消息都沒法消除。所以,在大多數狀況下,將選項傳遞給環境是最實用的方法。
警告以下:
./efficiency_guide.erl:60:警告:未優化:該函數返回了二進制
./efficiency_guide.erl:62:警告:已優化:匹配上下文已重用
爲了更清楚地說明警告所指的代碼,例如,如下示例中的警告以註釋的形式插入它們所引用的子句以後,例如:
after_zero (<< 0,T/binary>>) -> %% BINARY CREATED:從函數返回二進制
T;
after_zero (<< _,T/binary >>) -> %%優化:重用匹配上下文
after_zero(T);
after_zero (<< >>) ->
<< >>。
第一個子句的警告說,不能延遲子二進制的建立,由於它將被返回。第二個子句的警告說將不會建立子二進制。
編譯器會肯定變量是否未使用。爲如下每一個功能生成相同的代碼:
count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
count1(<<>>, Count) -> Count.
count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
count2(<<>>, Count) -> Count.
count3(<<_H,T/binary>>, Count) -> count3(T, Count+1);
count3(<<>>, Count) -> Count.
在每次迭代中,二進制中的前8位將被跳過,不匹配。
R12B中的二進制處理獲得了顯着改善。因爲在R11B中有效的代碼在R12B中可能無效,反之亦然,所以本《效率指南》的較早版本包含一些有關R11B中二進制處理的信息。