[英]Erlang, Last Call Optimization, lambda functions, and how to prevent growing a stack
我正在編寫一些Erlang代碼,但遇到一種我不理解的怪異情況。
編碼:
-module(recursive_test).
-export([a/2]).
a(_, []) -> ok;
a(Args, [H|T]) ->
F = fun() -> a(Args, T) end,
io:fwrite(
"~nH: ~p~nStack Layers: ~p",
[H, process_info(self(), stack_size)]
),
b(Args, F).
b(Args, F) ->
case Args of
true -> ok;
false -> F()
end.
輸出:
(Erlang/OTP 20)
12> c(recursive_test).
{ok,recursive_test}
13> recursive_test:a(false, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).
H: 1
Stack Layers: 28
H: 2
Stack Layers: 28
H: 3
Stack Layers: 28
H: 4
Stack Layers: 28
H: 5
Stack Layers: 28
H: 6
Stack Layers: 28
H: 7
Stack Layers: 28
H: 8
Stack Layers: 28
H: 9
Stack Layers: 28
H: 10
Stack Layers: 28
ok
14> recursive_test:a(false, [1, 2, 3, 4, 5, 6]).
H: 1
Stack Layers: 28
H: 2
Stack Layers: 28
H: 3
Stack Layers: 28
H: 4
Stack Layers: 28
H: 5
Stack Layers: 28
H: 6
Stack Layers: 28
ok
從我的理解這篇文章 ,二郎使用最后調用優化的地方,如果過去的事情的功能確實是調用另一個函數,該BeamVM反而會跳程序計數器的新功能的開始,而不是推一個新堆的幀。 這是否意味着在上述模式中,我們踩着堆而不是踩着堆? 這是否釋放了以前保存在這些函數中的內存(對於上述代碼,是一次在內存中分配一個函數F的副本,還是一次在內存中分配多個函數F的副本?一次)? 使用此模式是否有任何負面影響(除了明顯的調試困難之外)?
首先,由於Erlang的不可變性質, fun
(lambda,closure或任何您想稱呼的東西)可以並且可以像元組一樣想象實現
{fun, {Module, FuncRef, Arity, CodeVersion}, CapturedValues}
因此,在您的情況下,它將類似於
{fun, {recursive_test, '-a/2-fun-0-', 2, 2248400...}, [false, [2,3,4|...]]}
注意,因為您有0的fun
加上2個捕獲的值,所以arity為2。
然后,應該更容易理解代碼中正在發生的事情。 請記住,不是真正的元組,而是一些在數據消耗,堆術語引用,GC,圍繞Erlang分發協議進行傳輸等方面表現非常相似的結構。
讓您了解第二件事,即在b/2
中調用F()
類似於
recursive_test:'-a/2-fun-0-'(false, [2,3,4|...])
這可以是很好的尾叫又名跳轉。 因此,您的代碼使“元組”變得fun
,然后在代碼中跳來跳去。 然后不再引用每個fun
“元組”,因此可以隨時將其刪除。 我建議您嘗試使用數字而不是列表進行嘗試,並嘗試使用越來越大的數字,並使用process_info或觀察器觀察內存消耗。 這將是一個很好的練習。
可以使用的順便說一句
process_info(self(), stack_size)
而不是緩慢而丑陋
roplists:get_value(stack_size, process_info(self()))
這不是答案,但是您應該找到想要的東西。 我已經編譯了您的代碼的一個版本(沒有io:format調用第7行。然后,您可以反編譯Beam文件以查看代碼的解釋方式:
-module(recursive_test).
-export([a/2]).
a(_, []) -> ok;
a(Args, [H|T]) ->
F = fun() ->
a(Args, T)
end,
b(Args, F).
b(Args, F) ->
case Args of
true -> ok;
false -> F()
end.
在外殼中:
15> c(recursive_test).
recursive_test.erl:5: Warning: variable 'H' is unused
{ok,recursive_test}
16> rp(beam_disasm:file(recursive_test)).
{beam_file,recursive_test,
[{a,2,2},{module_info,0,10},{module_info,1,12}],
[{vsn,[224840029366305056373101858936888814401]}],
[{version,"7.2.1"},
{options,[]},
{source,"c:/git/fourretout/src/recursive_test.erl"}],
[{function,a,2,2,
[{label,1},
{line,1},
{func_info,{atom,recursive_test},{atom,a},2},
{label,2},
{test,is_nonempty_list,{f,3},[{x,1}]},
{allocate,1,2},
{get_tl,{x,1},{x,1}},
{move,{x,0},{y,0}},
{make_fun2,{recursive_test,'-a/2-fun-0-',2},0,88683754,2},
{move,{x,0},{x,1}},
{move,{y,0},{x,0}},
{call_last,2,{recursive_test,b,2},1},
{label,3},
{test,is_nil,{f,1},[{x,1}]},
{move,{atom,ok},{x,0}},
return]},
{function,b,2,5,
[{line,2},
{label,4},
{func_info,{atom,recursive_test},{atom,b},2},
{label,5},
{test,is_atom,{f,8},[{x,0}]},
{select_val,{x,0},
{f,8},
{list,[{atom,true},{f,6},{atom,false},{f,7}]}},
{label,6},
{move,{atom,ok},{x,0}},
return,
{label,7},
{allocate,0,2},
{move,{x,1},{x,0}},
{line,3},
{call_fun,0},
{deallocate,0},
return,
{label,8},
{line,4},
{case_end,{x,0}}]},
{function,module_info,0,10,
[{line,0},
{label,9},
{func_info,{atom,recursive_test},{atom,module_info},0},
{label,10},
{move,{atom,recursive_test},{x,0}},
{line,0},
{call_ext_only,1,{extfunc,erlang,get_module_info,1}}]},
{function,module_info,1,12,
[{line,0},
{label,11},
{func_info,{atom,recursive_test},{atom,module_info},1},
{label,12},
{move,{x,0},{x,1}},
{move,{atom,recursive_test},{x,0}},
{line,0},
{call_ext_only,2,{extfunc,erlang,get_module_info,2}}]},
{function,'-a/2-fun-0-',2,14,
[{line,5},
{label,13},
{func_info,{atom,recursive_test},{atom,'-a/2-fun-0-'},2},
{label,14},
{call_only,2,{recursive_test,a,2}}]}]}
ok
17>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.