Чи виконує Ruby оптимізацію хвостових дзвінків?


92

Функціональні мови призводять до використання рекурсії для вирішення багатьох проблем, і тому багато з них виконують Оптимізацію виклику хвоста (TCO). TCO викликає виклики функції з іншої функції (або її самої, в цьому випадку ця функція також відома як Усунення рекурсії хвоста, що є підмножиною TCO), як останній крок цієї функції, не потребуючи нового кадру стека, що зменшує накладні витрати та використання пам'яті.

Очевидно, Рубі "запозичив" низку концепцій з функціональних мов (лямбди, функції, як map тощо), що викликає у мене питання: чи виконує Ruby оптимізацію хвостових викликів?

Відповіді:


127

Ні, Рубі не виконує TCO. Однак він також не виконує TCO.

Специфікація мови Ruby нічого не говорить про TCO. Там не сказано, що ти повинен це робити, але також не сказано, що ти не можеш цього зробити. На це просто не можна покладатися .

Це на відміну від схеми, де Мовна специфікація вимагає, щоб усі реалізації мали виконувати TCO. Але це також несхоже на Python, де Гвідо ван Россум неодноразово чітко давав зрозуміти (востаннє всього пару днів тому), що реалізації Python не повинні виконувати TCO.

Юкіхіро Мацумото прихильно ставиться до TCO, він просто не хоче змушувати всі Реалізації підтримувати його. На жаль, це означає, що ви не можете покладатися на TCO, або якщо ви це зробите, ваш код більше не буде переноситися до інших реалізацій Ruby.

Отже, деякі реалізації Ruby виконують TCO, але більшість цього не робить. Наприклад, YARV підтримує TCO, хоча (на даний момент) вам потрібно явно розкомментирувати рядок у вихідному коді та перекомпілювати VM, щоб активувати TCO - у наступних версіях він буде ввімкнений за замовчуванням, після того, як реалізація докаже стабільний. Віртуальна машина Parrot підтримує TCO власним чином, тому кардинал також міг би її легко підтримати. CLR має певну підтримку TCO, а це означає, що IronRuby та Ruby.NET могли б це зробити. Можливо, це міг би зробити і Рубіній.

Але JRuby та XRuby не підтримують TCO, і, мабуть, не підтримуватимуть, якщо JVM не отримає підтримки TCO. Проблема полягає в наступному: якщо ви хочете мати швидку реалізацію та швидку та безперебійну інтеграцію з Java, то ви повинні бути сумісними зі стеком з Java і використовувати стек JVM якомога більше. Ви можете досить легко реалізувати TCO на батутах або явному стилі передачі продовження, але тоді ви більше не використовуєте стек JVM, що означає, що кожного разу, коли ви хочете зателефонувати в Java або зателефонувати з Java у Ruby, вам потрібно виконати якусь перетворення, що відбувається повільно. Отже, XRuby та JRuby вирішили піти зі швидкістю та інтеграцією Java над TCO та продовженнями (які в основному мають однакову проблему).

Це стосується всіх реалізацій Ruby, які хочуть щільно інтегруватися з якоюсь платформою хосту, яка не підтримує TCO. Наприклад, я думаю, у MacRuby буде така сама проблема.


2
Я можу помилитися (будь ласка, просвіти мене, якщо так), але я сумніваюся, що TCO має якийсь сенс у справжніх мовах OO, оскільки хвіст-виклик повинен мати можливість повторного використання кадру стека абонента. Оскільки при пізньому прив'язуванні невідомо під час компіляції, який метод буде викликаний під час надсилання повідомлення, здається важким переконатися, що (можливо, за допомогою JIT типу зворотного зв'язку, або примушуючи всіх реалізаторів повідомлення використовувати стекові кадри того самого розміру, або обмеженням TCO до самостійних надсилань того самого повідомлення ...).
Damien Pollet

2
Це чудовий відгук. Цю інформацію непросто знайти через Google. Цікаво, що yarv це підтримує.
Чарлі Флауерс

15
Демієне, виявляється, що TCO насправді потрібен для справжніх мов OO: див. Projectfortress.sun.com/Projects/Community/blog/… . Не хвилюйтеся занадто про речі стекових каркасів: цілком можливо розробити стекові кадри розумно, щоб вони добре працювали з TCO.
Тоні Гарнок-Джонс,

2
tonyg врятував посиланий пост GLS від вимирання, віддзеркаливши його тут: eighty-twenty.org/index.cgi/tech/oo-tail-calls-20111001.html
Френк Шеарар

Я виконую домашнє завдання, яке вимагає від мене розбору набору вкладених масивів довільної глибини. Очевидний спосіб зробити це рекурсивно, а подібні випадки використання в Інтернеті (які я можу знайти) використовують рекурсію. Моя конкретна проблема навряд чи підірветься навіть без TCO, але той факт, що я не можу написати повністю загальне рішення без переходу на ітерацію, мене турбує.
Ісаак Рабінович

42

Оновлення: Ось гарне пояснення TCO в Ruby: http://nithinbekal.com/posts/ruby-tco/

Оновлення: Ви можете також перевірити gem tco_method : http://blog.tdg5.com/introducing-the-tco_method-gem/

У МРТ Ruby (1.9, 2.0 та 2.1) ви можете увімкнути TCO за допомогою:

RubyVM::InstructionSequence.compile_option = {
  :tailcall_optimization => true,
  :trace_instruction => false
}

Була пропозиція включити TCO за замовчуванням у Ruby 2.0. Це також пояснює деякі проблеми, пов'язані з цим: Оптимізація виклику хвоста: увімкнути за замовчуванням ?.

Короткий уривок із посилання:

Як правило, оптимізація хвоста-рекурсія включає інший спосіб оптимізації - переклад "заклик" до "стрибка". На мою думку, важко застосувати цю оптимізацію, оскільки розпізнавання "рекурсії" важко у світі Рубі.

Наступний приклад. виклик методу fact () у реченні "else" не є "хвостовим викликом".

def fact(n) 
  if n < 2
    1 
 else
   n * fact(n-1) 
 end 
end

Якщо ви хочете скористатися оптимізацією зворотного виклику методом fact (), вам потрібно змінити метод fact () наступним чином (стиль передачі продовження).

def fact(n, r) 
  if n < 2 
    r
  else
    fact(n-1, n*r)
  end
end

12

Він може мати, але не гарантується:

https://bugs.ruby-lang.org/issues/1256


Наразі посилання мертве.
karatedog

@karatedog: дякую, оновлено. Хоча, чесно кажучи, посилання, мабуть, застаріло, оскільки зараз помилці 5 років, і з тих пір проводиться діяльність з цієї ж теми.
Steve Jessop

Так :-) Я щойно прочитав цю тему і побачив, що в Ruby 2.0 він може ввімкнутись із вихідного коду (більше немає модифікації та перекомпіляції джерела C).
karatedog


2

Це спирається на відповіді Йорга та Ернеста. В основному це залежить від реалізації.

Я не міг отримати відповідь Ернеста для роботи над МРТ, але це можливо. Я знайшов цей приклад, який працює для МРТ 1.9 до 2.1. Це має надрукувати дуже велику кількість. Якщо ви не встановите для параметра TCO значення true, ви повинні отримати помилку "занадто глибокий стек".

source = <<-SOURCE
def fact n, acc = 1
  if n.zero?
    acc
  else
    fact n - 1, acc * n
  end
end

fact 10000
SOURCE

i_seq = RubyVM::InstructionSequence.new source, nil, nil, nil,
  tailcall_optimization: true, trace_instruction: false

#puts i_seq.disasm

begin
  value = i_seq.eval

  p value
rescue SystemStackError => e
  p e
end
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.