Чому Java взагалі не має оптимізації для хвостової рекурсії?


92

З того, що я прочитав: Причина полягає в тому, що не просто визначити, який метод насправді буде називатися так, як ми маємо спадщину.

Однак чому Java принаймні не має оптимізацію хвостових рекурсій для статичних методів і не застосовує належний спосіб викликати статичні методи за допомогою компілятора?

Чому Java взагалі не має підтримки для рекурсії хвоста?

Я не впевнений, чи є тут взагалі якісь труднощі.


Щодо запропонованого дубліката , як пояснив Йорг W Міттаг 1 :

  • Інше питання задається про TCO, це про TRE. TRE набагато простіше, ніж TCO.
  • Крім того, інше питання задає питання про обмеження, які JVM накладає на мовні реалізації, які бажають компілювати до JVM, це питання задає Java, яка є однією мовою, яка не обмежена JVM, оскільки специфікацію JVM можна змінити ті ж люди, які розробляють Java.
  • І, нарешті, у JVM немає навіть обмежень щодо TRE, тому що в JVM є внутрішній метод GOTO, який є усім необхідним для TRE

1 Додано форматування для виклику очок.


26
Процитуючи Еріка Ліпперта : "Особливості недешеві; вони надзвичайно дорогі, і вони повинні не тільки виправдовувати власні витрати, але й виправдовувати альтернативні витрати, не виконуючи сто інших можливостей, які ми могли зробити з цим бюджетом". Java була розроблена частково для звернення до розробників C / C ++, і оптимізація хвостових рекурсій на цих мовах не гарантована.
Doval

5
Виправте мене, якщо я помиляюся, але чи Ерік Ліпперт є дизайнером для C #, який має оптимізацію хвостової рекурсії?
ПоінформованоA4

1
Він в команді компілятора C #, так.
Doval

10
Як я розумію, JIT може це зробити , якщо певні умови будуть дотримані , можливо. Тож на практиці ви не можете покластися на це. Але те, чи є у C # чи ні, це не має значення для Еріка.
Doval

4
@InstructedA Якщо ви копаєте трохи глибше, ви можете побачити, що це ніколи не робиться в 32-бітному компіляторі JIT. 64-розрядний JIT є більш сучасним та розумним у багатьох аспектах. Ще новіший експериментальний компілятор (як для 32, так і для 64-розрядних) ще розумніший, і він підтримуватиме оптимізацію хвостової рекурсії в IL, яка прямо не вимагає цього. Є ще один момент, який потрібно врахувати - у JIT-компіляторів не так багато часу. Вони сильно оптимізовані для швидкості - застосуванню, яке може зайняти години, щоб зібрати в C ++, все одно потрібно буде перейти з IL до рідного за пару сотень мс, максимум (принаймні частково).
Луань

Відповіді:


132

Як пояснив у цьому відео Брайан Гец (Java Language Architect в Oracle) :

у класах jdk [...] існує ряд методів, що залежать від безпеки, які покладаються на підрахунок кадрів стека між кодом бібліотеки jdk та кодом виклику, щоб з'ясувати, хто їх викликає.

Все, що змінило кількість кадрів на стеку, це порушить і спричинить помилку. Він визнає, що це було дурною причиною, і тому розробники JDK відтоді замінили цей механізм.

Далі він зазначає, що це не пріоритет, а рецидиви хвоста

врешті-решт закінчиться.

Примітка Це стосується HotSpot та OpenJDK, інші VM можуть відрізнятися.


7
Я вражений, що існує така досить різана і висушена відповідь, як ця! Але насправді здається, що відповідь - це можливо вже зараз, з застарілих технічних причин цього ще не було зроблено, і тому ми просто чекаємо, коли хтось вирішить, що це досить важливо для реалізації.
BrianH

1
Чому б не здійснити рішення? Наче мітка-goto під кришкою-обкладинкою, яка просто переходить на вершину виклику методу з новими значеннями аргументів? Це було б оптимізацією часу компіляції і не потрібно було б зміщувати рамки стека або спричиняти порушення безпеки.
Джеймс Уоткінс

2
Нам буде набагато краще, якщо ви чи хтось інший тут зможете копати глибше і конкретно надавати, що таке "методи, що залежать від безпеки". Дякую!
Поінформовано

3
@InstructedA - см securingjava.com/chapter-three/chapter-three-6.html , який містить докладний опис того , як система менеджер Java Security працював біля виходу Java 2.
Жюль

3
Одне вирішення полягає у використанні іншої мови JVM. Скала, наприклад, робить це в компіляторі, а не під час виконання.
Джеймс Мур

24

У Java немає оптимізації хвостових викликів з тієї самої причини, чому у більшості імперативних мов немає. Імперативні петлі є кращим стилем мови, і програміст може замінити хвостову рекурсію імперативними петлями. Складність не варта для функції, використання якої не відштовхується від стилю.

Ця річ, коли програмісти хочуть іноді писати у FP-стилі інакше вкрай необхідними мовами, увійшли в моду лише протягом останніх 10 років або близько того, після того, як комп'ютери почали масштабувати в ядрах замість ГГц. Навіть зараз це не так популярно. Якби я запропонував замінити імперативну петлю на рецидиві хвоста на роботі, половина рецензентів на код посміялася б, а друга половина видала блуканий вигляд. Навіть у функціональному програмуванні ти, як правило, уникаєш хвостової рекурсії, якщо інші конструкції, такі як функції вищого порядку, не підходять чітко.


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

4
@amon Джеймс Гослінг одного разу сказав, що він не додасть функції на Java, якщо її не попросять декілька людей , і лише тоді він вважатиме це. Тож я не здивуюся, якби частина відповіді насправді була "ти завжди можеш використовувати forцикл" (і так само для першокласних функцій проти об'єктів). Я б не пішов так далеко, щоб назвати це "імперативною фанатизмою", але я не думаю, що це було б дуже затребуваним ще в 1995 році, коли головним занепокоєнням були, швидше за все, швидкість Java та відсутність генеричних даних.
Doval

7
@amon, модель безпеки вважає мене законною причиною не додавати TCO до раніше існуючої мови, але погана причина, щоб не спроектувати її в мову в першу чергу. Ви не викидаєте головну програму, помітну програмісту, щоб включити незначну позакулісну функцію. "Чому у Java 8 немає TCO" - це зовсім інше питання, ніж "Чому у Java 1.0 не було TCO?" Я відповідав на останнє.
Карл Білефельдт

3
@rwong, ви можете перетворити будь-яку рекурсивну функцію в ітеративну. (Якщо я можу, я написав приклад тут )
Alain

3
@ZanLynx це залежить від типу проблеми. Наприклад, модель відвідувачів може скористатися TCO (залежно від специфіки структури та відвідувача), не маючи нічого спільного з FP. Перехід через державні машини схожий, хоча трансформація за допомогою батутів здається дещо природнішою (залежно від проблеми). Крім того, хоча ви можете технічно реалізовувати дерева за допомогою циклів, я вважаю, що консенсус полягає в тому, що рекурсія є набагато природнішою (аналогічно для DFS, хоча в цьому випадку ви можете уникнути рекурсії через обмеження стека навіть при TCO).
Maciej Piechotka

5

У Java немає оптимізації високих викликів, оскільки JVM не має байт-коду для хвостових викликів (до деякого статично невідомого вказівника функції, наприклад, методу в деяких vtable).

Це схоже на соціальні (а може бути і технічні) причини, додавання нової операції байт-коду в JVM (що зробило б її несумісною з попередніми версіями цього JVM) дуже важко для власника специфікації JVM.

Технічні причини не додавання нового байтового коду у специфікацію JVM включають той факт, що реальні реалізації JVM є надзвичайно складними програмними програмами (наприклад, через безліч оптимізацій JIT, які вони роблять).

Хвостові дзвінки до якоїсь невідомої функції вимагають заміни поточного кадру стека на новий, і ця операція повинна знаходитися в JVM (справа не лише в зміні компілятора генерації байт-коду).


17
Питання не в хвостових викликах, а в рецидиві хвоста. І питання не про мову байт-коду JVM, а про мову програмування Java, яка є зовсім іншою мовою. Scala компілюється в байт-код JVM (серед інших) і має усунення хвостової рекурсії. Реалізації схеми на JVM мають повні належні виклики. Java 7 (JVM Spec, 3-е вид.) Додала новий байт-код. J9 JVM IBM виконує TCO, навіть не потребуючи спеціального байтового коду.
Йорг W Міттаг

1
@ JörgWMittag: Це правда, але тоді мені цікаво, чи справді це так, що мова програмування Java не має належних хвостових дзвінків. Це могло б бути більш точним стверджувати , що мова програмування Java не повинні мати відповідні виклики хвоста, в тому , що нічого в специфікації мандатів його. (Тобто: я не впевнений, що що-небудь в специфікації фактично забороняє імплементацію усунення хвостових викликів. Про це просто не згадується.)
ruakh

4

Якщо мова не має спеціального синтаксису для здійснення хвостового виклику (рекурсивний або іншим способом), і компілятор буде збиватися, коли запит хвоста вимагається, але не може бути згенерований, "необов'язковий" хвіст-виклик або хвіст-рекурсійна оптимізація призведе до ситуацій, коли фрагмент коду може вимагати менше 100 байт стека на одній машині, але більше 100 000 000 байт стека на іншій. Такого різного слід вважати якісним, а не просто кількісним.

Очікується, що машини можуть мати різні розміри стека, і тому код завжди може працювати на одній машині, але дути стек на іншій. Однак, як правило, код, який буде працювати на одній машині, навіть коли стек штучно обмежений, швидше за все, буде працювати на всіх машинах із "нормальними" розмірами стека. Якщо, однак, метод, що повторює 1 000 000 глибин, є хвостовим викликом, оптимізованим на одній машині, а не на іншій, виконання на колишній машині, швидше за все, спрацює, навіть якщо його стек є незвично малим, а на останньому виходить з ладу, навіть якщо його стек незвично великий .


2

Я думаю, що в Java не використовується рекурсія хвостових викликів, головним чином тому, що це змінить сліди стека і, таким чином, зробить налагодження програми набагато складніше. Я думаю, що однією з головних цілей Java є дозволити програмістам легко налагоджувати свій код, а відстеження стеків має важливе значення для цього, особливо у високо об'єктно орієнтованому середовищі програмування. Оскільки ітерація може бути використана натомість, мовний комітет, мабуть, подумав, що не варто додавати хвостову рекурсію.

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