我正在编写一些 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
根据我从这篇文章的理解,Erlang 使用了最后调用优化,如果一个函数做的最后一件事是调用另一个函数,那么 BeamVM 会将程序计数器跳转到新函数的开头,而不是推送一个新堆栈框架。这是否意味着在像上面这样的模式中,我们在堆周围而不是堆栈周围踩踏?这是否释放了之前在这些函数中保存的内存(在上述代码的情况下,我们是否会一次在内存中分配一个函数 F 的副本,或者我们是否会在内存中分配多个函数 F 的副本?一次)?使用这种模式是否有任何负面影响(除了明显的调试困难)?