2

我正在编写一些 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 的副本?一次)?使用这种模式是否有任何负面影响(除了明显的调试困难)?

4

2 回答 2

2

首先,fun由于 Erlang 的不可变特性,(lambda、闭包或任何您想称呼它的名称)可以并且以您可以想象的方式实现,例如元组

{fun, {Module, FuncRef, Arity, CodeVersion}, CapturedValues}

所以在你的情况下,它会像

{fun, {recursive_test, '-a/2-fun-0-', 2, 2248400...}, [false, [2,3,4|...]]}

请注意,arity 是 2,因为您有 arity 0fun加上 2 个捕获的值。

然后应该更容易理解代码中发生的事情。记住不是真正的元组,而是一些在数据消耗、堆项引用、GC、围绕 Erlang 分发协议的传输等方面表现非常相似的结构。

这使您可以理解第二件事,即F()在内部调用b/2类似于

recursive_test:'-a/2-fun-0-'(false, [2,3,4|...])

这可以是很好的尾调用又名跳跃。所以你的代码正在制作fun“元组”,然后只是跳来跳去代码。fun然后不再引用每个“元组”,因此可以随时被 GC 淘汰。我建议您尝试使用数字而不是列表,并尝试越来越大的数字并使用 process_info 或观察者观察内存消耗。这将是一个很好的练习。

顺便说一句,您可以使用

process_info(self(), stack_size)

而不是缓慢而丑陋

roplists:get_value(stack_size, process_info(self()))
于 2018-08-31T06:37:47.587 回答
1

这不是一个答案,但你应该找到你正在寻找的东西。我已经编译了您的代码版本(没有 io:format 调用第 7 行。然后您可以反编译光束文件以查看如何解释代码:

-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>
于 2018-08-29T07:17:16.060 回答