elixir: почему хвостовая рекурсия использует больше памяти, чем основная рекурсивная функция? - PullRequest
2 голосов
/ 06 мая 2020

Я сейчас читаю Learn Functional Programming with Elixir, в главе 4 автор говорит об оптимизации хвостового вызова, что хвостовая рекурсивная функция будет использовать меньше памяти, чем основная рекурсивная функция. Но когда я попробовал примеры из книги, результат оказался противоположным. используйте около 1 г. Я что-то не понимаю?

1 Ответ

5 голосов
/ 06 мая 2020

TL; DR: виртуальная машина, кажется, оптимизирует как совокупную стоимость владения.

В настоящее время компиляторы и виртуальные машины слишком умны, чтобы предсказать их поведение. Преимущество хвостовой рекурсии не в меньшем потреблении памяти , а в:

Это необходимо для того, чтобы гарантировать, что системные ресурсы, например стек вызовов, не будут

Если вызов не является хвостовым рекурсивным, стек должен сохраняться при вызовах. Рассмотрим следующий пример.

▶ 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...