¿Por qué la JVM todavía no admite la optimización de llamadas finales?

95

Dos años después de does-the-jvm-prevent-tail-call-optimizations , parece haber una implementación de prototipo y MLVM ha incluido la característica como "proto 80%" desde hace algún tiempo.

¿No hay un interés activo por parte de Sun / Oracle en admitir llamadas de cola o es solo que las llamadas de cola están "[...] predestinadas a ocupar el segundo lugar en cada lista de prioridades de funciones [...]" como se menciona en la JVM Cumbre de Idiomas ?

Me interesaría mucho si alguien hubiera probado una compilación de MLVM y pudiera compartir algunas impresiones de lo bien que funciona (si es que funciona).

Actualización: tenga en cuenta que algunas máquinas virtuales como Avian admiten llamadas de cola adecuadas sin ningún problema.

soc
fuente
14
Con el éxodo informado de la gente de Sun de Oracle, no esperaría que ninguno de los proyectos actuales continúe a menos que Oracle lo indique explícitamente :(
Thorbjørn Ravn Andersen
16
Tenga en cuenta que su respuesta aceptada es completamente incorrecta. No existe un conflicto fundamental entre la optimización de la llamada de cola y la OOP y, por supuesto, varios lenguajes como OCaml y F # tienen OOP y TCO.
JD
2
Bueno, llamar a los idiomas OCaml y F # OOP es una broma de mal gusto en primer lugar. Pero sí, OOP y TCO no tienen mucho en común, excepto el hecho de que el tiempo de ejecución tiene que verificar que el método que se está optimizando no esté anulado / subclasificado en otro lugar.
soc
5
+1 Viniendo de un entorno C, siempre asumí que el TCO era un hecho en cualquier JVM moderna. Nunca se me ocurrió comprobar realmente y cuando lo hice los resultados fueron sorprendentes ...
thkala
2
@soc: "excepto el hecho de que el tiempo de ejecución tiene que verificar que el método que se está optimizando no esté anulado / subclasificado en otro lugar". Tu "hecho" es una completa tontería.
JD

Respuestas:

32

Diagnóstico de código Java: Mejorar el rendimiento de su código Java ( alt ) explica por qué la JVM no admite la optimización de llamadas finales.

Pero aunque se sabe cómo transformar automáticamente una función recursiva de cola en un bucle simple, la especificación de Java no requiere que se realice esta transformación. Presumiblemente, una de las razones por las que no es un requisito es que, en general, la transformación no se puede realizar de forma estática en un lenguaje orientado a objetos. En cambio, la transformación de una función recursiva de cola a un bucle simple debe realizarse dinámicamente mediante un compilador JIT.

Luego da un ejemplo de código Java que no se transforma.

Entonces, como muestra el ejemplo del Listado 3, no podemos esperar que los compiladores estáticos realicen la transformación de la recursividad de la cola en el código Java mientras preservan la semántica del lenguaje. En cambio, debemos confiar en la compilación dinámica por parte del JIT. Dependiendo de la JVM, el JIT puede o no hacer esto.

Luego proporciona una prueba que puede utilizar para averiguar si su JIT hace esto.

Naturalmente, dado que se trata de un documento de IBM, incluye un enchufe:

Ejecuté este programa con un par de SDK de Java y los resultados fueron sorprendentes. La ejecución en Sun's Hotspot JVM para la versión 1.3 revela que Hotspot no realiza la transformación. En la configuración predeterminada, el espacio de la pila se agota en menos de un segundo en mi máquina. Por otro lado, la JVM de IBM para la versión 1.3 ronronea sin problemas, lo que indica que transforma el código de esta manera.

emory
fuente
62
FWIW, las llamadas de cola no se tratan solo de funciones auto-recursivas como él implica. Las llamadas de cola son las llamadas a funciones que aparecen en la posición de cola. No tienen que ser llamadas a uno mismo ni tienen que ser llamadas a ubicaciones conocidas estáticamente (por ejemplo, pueden ser llamadas a métodos virtuales). El problema que describe no es un problema si la optimización de las llamadas finales se realiza correctamente en el caso general y, en consecuencia, su ejemplo funciona perfectamente en lenguajes orientados a objetos que admiten llamadas finales (por ejemplo, OCaml y F #).
JD
3
"debe ser realizado dinámicamente por un compilador JIT" lo que significa que debe ser realizado por la propia JVM en lugar del compilador Java. Pero el OP está preguntando por la JVM.
Raedwald
11
"en general, la transformación no se puede realizar de forma estática en un lenguaje orientado a objetos". Esta es una cita, por supuesto, pero cada vez que veo tal excusa me gustaría preguntar sobre los números, porque no me sorprendería si en la práctica, en la mayoría de los casos, pudiera establecerse en tiempo de compilación.
greenoldman
5
El enlace al artículo citado ahora está roto, aunque Google lo tiene en caché. Más importante aún, el razonamiento del autor es erróneo. El ejemplo dado podría ser optimizado para llamadas finales, usando compilación estática y no solo dinámica, si solo el compilador insertara una instanceofverificación para ver si thises un Exampleobjeto (en lugar de una subclase de Example).
Alex D
4
simplemente enlace a webarchive web.archive.org/web/20120506085636/http://www.ibm.com/…
Suvitruf - Andrei Apanasik
30

Una razón que he visto en el pasado para no implementar el TCO (y que se considera difícil) en Java es que el modelo de permisos en la JVM es sensible a la pila y, por lo tanto, las llamadas finales deben manejar los aspectos de seguridad.

Creo que Clements y Felleisen [1] [2] demostraron que esto no es un obstáculo y estoy bastante seguro de que el parche MLVM mencionado en la pregunta también lo trata.

Me doy cuenta de que esto no responde a su pregunta; simplemente agregando información interesante.

  1. http://www.ccs.neu.edu/scheme/pubs/esop2003-cf.pdf
  2. http://www.ccs.neu.edu/scheme/pubs/cf-toplas04.pdf
Alex Miller
fuente
1
+1. Escuche las preguntas / respuestas al final de esta presentación de Brian Goetz youtube.com/watch?v=2y5Pv4yN0b0&t=3739
mcoolive
15

Quizás ya sepa esto, pero la característica no es tan trivial como puede parecer, ya que el lenguaje Java realmente expone el seguimiento de la pila al programador.

Considere el siguiente programa:

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());
    }
}

Aunque tiene una "llamada de cola", es posible que no se optimice. (Si está optimizado, aún requiere la contabilidad de toda la pila de llamadas, ya que la semántica del programa se basa en ella).

Básicamente, esto significa que es difícil admitir esto sin dejar de ser compatible con versiones anteriores.

aioobe
fuente
17
Encontré el error en su pensamiento: "requiere la contabilidad de toda la pila de llamadas ya que la semántica del programa se basa en ella". :-) Es como las nuevas "excepciones suprimidas". Los programas que se basan en tales cosas están destinados a fallar. En mi opinión, el comportamiento del programa es absolutamente correcto: tirar marcos de pila es de lo que se tratan las llamadas finales.
soc
4
@Marco, pero casi cualquier método podría generar una excepción, desde la cual la pila de llamadas completa estará disponible, ¿verdad? Además, no puede decidir de antemano qué métodos llamarán indirectamente gen este caso ... piense en el polimorfismo y la reflexión, por ejemplo.
aioobe
2
Es un efecto secundario causado por la adición de ARM en Java 7. Es un ejemplo de que no puede confiar en las cosas que mostró anteriormente.
soc
6
"el hecho de que el lenguaje exponga la pila de llamadas hace que sea difícil implementar esto": ¿el lenguaje requiere que el seguimiento de la pila devuelto por getStackTrace()de un método x()que el código fuente muestra que se llama desde un método y()también muestra que x()se llamó desde y()? Porque si hay algo de libertad, no hay problema real.
Raedwald
8
Esto es simplemente una cuestión de redactar la especificación de un solo método, desde "le da todos los marcos de pila" hasta "le da todos los marcos de pila activos, dejando fuera los obsoletos por las llamadas de cola". Además, se podría convertir en un conmutador de línea de comandos o una propiedad del sistema si se respeta la llamada de cola.
Ingo
12

Java es el lenguaje menos funcional que puedas imaginar (bueno, de acuerdo, ¡ quizás no !) Pero esto sería una gran ventaja para los lenguajes JVM, como Scala , que lo son.

Mis observaciones son que hacer de la JVM una plataforma para otros lenguajes nunca pareció estar en la parte superior de la lista de prioridades para Sun y supongo que ahora para Oracle.

oxbow_lakes
fuente
16
@ Thorbjørn - Escribí un programa para predecir si un programa dado se detendría en un período de tiempo finito. ¡Me tomó años !
oxbow_lakes
3
Los primeros BASIC que utilicé no tenían funciones, sino GOSUB y RETURN. Tampoco creo que LOLCODE sea muy funcional (y puedes tomar eso en dos sentidos).
David Thornley
1
@David, funcional! = Tiene funciones.
Thorbjørn Ravn Andersen
2
@ Thorbjørn Ravn Andersen: No, pero es una especie de requisito previo, ¿no crees?
David Thornley
3
"Hacer de la JVM una plataforma para otros lenguajes nunca pareció estar en la parte superior de la lista de prioridades de Sun". Se esforzaron considerablemente más en hacer de la JVM una plataforma para lenguajes dinámicos que para lenguajes funcionales.
JD