TL; DR: erlang виртуальная машина, кажется, оптимизирует как совокупную стоимость владения.
В настоящее время компиляторы и виртуальные машины слишком умны, чтобы предсказать их поведение. Преимущество хвостовой рекурсии не в меньшем потреблении памяти , а в:
Это необходимо для того, чтобы гарантировать, что системные ресурсы, например стек вызовов, не будут
Если вызов не является хвостовым рекурсивным, стек должен сохраняться при вызовах. Рассмотрим следующий пример.
▶ defmodule NTC do
def inf do
inf()
IO.puts(".")
DateTime.utc_now()
end
end
Здесь нам нужно сохранить стек, чтобы можно было продолжить выполнение вызывающего , когда рекурсия вернется. Не будет, потому что эта рекурсия бесконечна. Компилятор не может его оптимизировать, и вот что мы получаем:
▶ NTC.inf
[1] 351729 killed iex
Обратите внимание, что ничего не вышло , что означает, что мы рекурсивно вызывали себя, пока стек не взорвался. . С TCO возможна бесконечная рекурсия (и она широко используется при обработке сообщений).
Возвращаясь к вашему примеру. Как мы видели, совокупная стоимость владения была произведена в обоих случаях (иначе мы бы столкнулись с переполнением стека), и в первом случае аккумулятор хранится в специальной переменной, а во втором - только возвращаемое значение в стеке. Этот выигрыш вы видите: эликсир является неизменным, и содержимое переменной (которое огромно для факториала 200К) копируется и сохраняется в памяти для каждого вызова.
Примечание:
Вы можете разобрать оба модуля с помощью :erts_debug.df(Factorial)
(что создаст Elixir.Factorial.dis
файлов в одном каталоге) и увидеть, что вызовы были неявно связаны с TCO.