Erlang, Last Call Optimization, лямбда-функции и как предотвратить рост стека - PullRequest
0 голосов
/ 29 августа 2018

Я пишу некоторый код 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, выделенных в памяти одновременно)? Есть ли какие-либо негативные последствия для использования этого шаблона (кроме очевидной борьбы с отладкой)?

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

Во-первых, fun (лямбда, замыкание или как вы хотите это называть) из-за неизменности Эрланга может быть реализовано так, как вы можете представить себе как кортеж

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

Так что в вашем случае это будет что-то вроде

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

Обратите внимание, что арность равна 2, потому что у вас арность 0 из fun плюс 2 захваченных значения.

Тогда вам будет проще понять, что происходит в вашем коде. Помните, что это не настоящий кортеж, а некоторая структура, которая ведет себя очень схожим образом в отношении потребления данных, ссылок на кучи, GC, передачи по протоколу распределения Erlang и т. Д.

Который позволяет вам понять вторую вещь, которая заключается в том, что вызов F() внутри b/2 является чем-то вроде

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()))
0 голосов
/ 29 августа 2018

Это не ответ, но вы должны найти то, что ищете. Я скомпилировал версию вашего кода (без строки вызова 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>
...