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


95

Через два роки після того, як робить-jvm-предотвратить-хвост-оптимізацію викликів , здається, є реалізація прототипу, і MLVM вже деякий час перелічує цю функцію як "прото 80%".

Чи немає активного інтересу з боку Sun / Oracle у підтримці хвостових викликів, чи просто, що хвостові виклики "[...] судилися виходити на друге місце у кожному списку пріоритетів функцій [...]", як згадувалося в JVM Мовний саміт ?

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

Оновлення: Зверніть увагу, що деякі віртуальні машини, такі як Avian, без проблем підтримують правильні зворотні дзвінки.


14
З урахуванням повідомлення про вихід людей із Сонця з Oracle, я б не очікував, що будь-який з поточних проектів продовжиться, якщо це прямо не сказано від Oracle :(
Торбьорн Равн Андерсен,

16
Зверніть увагу, що ваша прийнята відповідь є абсолютно неправильною. Немає принципового конфлікту між оптимізацією хвостових викликів та ООП, і, звичайно, деякі мови, такі як OCaml та F #, мають як ООП, так і ТШО.
JD

2
Що ж, називати мови OCaml та F # OOP - це насамперед поганий жарт. Але так, ООП і ТШО мають не так багато спільного, за винятком того факту, що час виконання повинен перевіряти, що метод, який оптимізується, не замінений / не класифікований десь ще.
soc

5
+1 Виходячи з фона C, я завжди припускав, що TCO є даним у будь-якій сучасній JVM. Мені навіть в
голову

2
@soc: "за винятком того факту, що середовище виконання має перевіряти, що метод, який оптимізується, не замінений / не підкласифікований де-небудь ще". Ваш "факт" - суцільна нісенітниця.
JD

Відповіді:


32

Діагностування коду Java: Покращення продуктивності коду Java ( alt ) пояснює, чому JVM не підтримує оптимізацію зворотних викликів.

Але хоча добре відомо, як автоматично перетворити хвостову рекурсивну функцію в простий цикл, специфікація Java не вимагає здійснення цього перетворення. Імовірно, одна з причин, що це не є вимогою, полягає в тому, що загалом трансформація не може бути здійснена статично об'єктно-орієнтованою мовою. Натомість перетворення з хвостово-рекурсивної функції на простий цикл має виконуватися динамічно компілятором JIT.

Потім він подає приклад коду Java, який не трансформується.

Отже, як показує приклад у лістингу 3, ми не можемо очікувати, що статичні компілятори виконуватимуть трансформацію рекурсії хвоста в коді Java, зберігаючи при цьому семантику мови. Натомість ми повинні покладатися на динамічну компіляцію JIT. Залежно від JVM, JIT може це робити, а може і не робити.

Потім він дає тест, за допомогою якого можна з’ясувати, чи робить це ваш JIT.

Звичайно, оскільки це папір IBM, він містить штекер:

Я запустив цю програму з декількома Java SDK, і результати були несподіваними. Запуск на Sun's Hotspot JVM для версії 1.3 показує, що Hotspot не виконує перетворення. За замовчуванням параметри стека на моєму комп'ютері вичерпуються менш ніж за секунду. З іншого боку, JVM від IBM для версії 1.3 без проблем мурчить, вказуючи, що вона перетворює код таким чином.


62
FWIW, хвостові дзвінки стосуються не лише саморекурсивних функцій, як він передбачає. Хвостові виклики - це будь-які виклики функцій, які відображаються в положенні хвоста. Вони не повинні бути дзвінками до себе, і вони не повинні бути дзвінками до статично відомих місць (наприклад, це можуть бути виклики віртуальних методів). Проблема, яку він описує, не є проблемою, якщо оптимізація хвостових викликів зроблена належним чином у загальному випадку, і, отже, його приклад ідеально працює в об'єктно-орієнтованих мовах, які підтримують хвостові виклики (наприклад, OCaml та F #).
JD

3
"повинен виконуватися динамічно компілятором JIT", що означає, що це повинен робити сам JVM, а не компілятор Java. Але ОП запитує про JVM.
Raedwald,

11
"загалом, перетворення не може бути здійснено статично об'єктно-орієнтованою мовою". Звичайно, це цитата, але кожного разу, коли я бачу такий виправдання, я хотів би запитати про цифри - бо я не здивувався б, якби на практиці в більшості випадків це можна було встановити під час компіляції.
greenoldman

5
Посилання на цитовану статтю зараз недійсне, хоча Google справді кешує. Що ще важливіше, міркування автора є хибними. Наведений приклад може бути оптимізований за допомогою зворотного виклику, використовуючи статичну, а не просто динамічну компіляцію, якби тільки компілятор вставляв instanceofперевірку, чи thisє Exampleоб'єктом (а не підкласом Example).
Alex D


30

Однією з причин, яку я бачив у минулому за нереалізацію TCO (і це сприймається як складно) в Java, є те, що модель дозволу в JVM є чутливою до стеку, і тому зворотні виклики повинні обробляти аспекти безпеки.

Я вважаю, що Клементс та Феллейзен [1] [2] показали, що це не є перешкодою, і я майже впевнений, що патч MLVM, згаданий у питанні, також має справу з цим.

Я розумію, що це не відповідає на ваше запитання; просто додавши цікаву інформацію.

  1. http://www.ccs.neu.edu/scheme/pubs/esop2003-cf.pdf
  2. http://www.ccs.neu.edu/scheme/pubs/cf-toplas04.pdf

1
+1. Прослухайте запитання / відповіді наприкінці цієї презентації Брайана Гетца youtube.com/watch?v=2y5Pv4yN0b0&t=3739
mcoolive

15

Можливо, ви це вже знаєте, але ця функція не така тривіальна, як може здатися, оскільки мова Java фактично надає програмісту трасування стека.

Розглянемо таку програму:

public class Test {

    public static String f() {
        String s = Math.random() > .5 ? f() : g();
        return s;
    }

    public static String g() {
        if (Math.random() > .9) {
            StackTraceElement[] ste = new Throwable().getStackTrace();
            return ste[ste.length / 2].getMethodName();
        }
        return f();
    }

    public static void main(String[] args) {
        System.out.println(f());
    }
}

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

По суті, це означає, що важко підтримати це, залишаючись назад сумісним.


17
Знайшов помилку у вашій думці: "вимагає ведення бухгалтерського обліку всього стеку викликів, оскільки семантика програми покладається на нього". :-) Це як нові "придушені винятки". Програми, що покладаються на такі речі, обов’язково зламаються. На мою думку, поведінка програми абсолютно коректна: викидання кадрів стека - це головне, що стосується викликів хвоста.
soc

4
@Marco, але майже будь-який метод може створити виняток, з якого весь стек викликів повинен бути доступним, чи не так? Крім того, ви не можете заздалегідь вирішити, які методи побічно викликатимуть gу цьому випадку ... подумайте, наприклад, про поліморфізм та рефлексію.
aioobe

2
Це побічний ефект, викликаний додаванням ARM в Java 7. Це приклад того, що ви не можете покладатися на такі речі, які ви показали вище.
soc

6
"той факт, що мова виставляє стек викликів, ускладнює його реалізацію": чи вимагає мова, щоб стек-трасування, повернутий getStackTrace()із методу, x()який показує вихідний код, викликався із методу, y()також показує, з якого x()був викликаний y()? Оскільки якщо є певна свобода, то справжньої проблеми немає.
Raedwald,

8
Це лише питання формулювання специфікації одного методу, починаючи від "дає вам всі стекові кадри" до "дає вам усі активні стекові кадри, не враховуючи застарілих із викликів хвоста". Крім того, можна зробити перемикачем командного рядка або властивістю системи, чи виконується tail-call.
Інго

12

Java є найменш функціональною мовою, яку ви могли собі уявити (ну добре, можливо, ні !), Але це було б великою перевагою для мов JVM, таких як Scala .

Мої спостереження полягають у тому, що зробити JVM платформою для інших мов ніколи не здавалося на першому місці в списку пріоритетів для Sun, і, мабуть, зараз для Oracle.


16
@ Thorbjørn - Я написав програму, щоб передбачити, чи зупиниться яка-небудь дана програма через певний проміжок часу. Мені зайняли віки !
oxbow_lakes

3
Перші ОСНОВНІ, якими я користувався, не мали функцій, а скоріше GOSUB та RETURN. Я також не думаю, що LOLCODE є надто функціональним (і ви можете прийняти це у двох сенсах).
Девід Торнлі,

1
@David, функціональний! = Має функції.
Thorbjørn Ravn Andersen

2
@ Торбьєрн Равн Андерсен: Ні, але це якась необхідна умова, чи не скажете ви?
Девід Торнлі,

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