13 de Mayo, 2004

Máquina virtual Java o código nativo

Leyendo Libertonia me he encontrado con un artículo, Compilando código Java, en que se realiza una pequeña comparativa entre la JVM de Sun y el compilador gcj de GNU.

Pues bueno, he ajustado un poco mi Simple Forth Interpreter para realizar mi propia prueba de bytecode interpretado vs código nativo.

El ajuste ha sido necesario porque mi gcj es el que viene con Debian 3.0 y es el 3.0.4. Creo que la versión actual del compilador no tiene el problema, pero vamos... lo que tengo para probar no soporta matches en un java.lang.String.

Como no lo usamos en la prueba, se comenta el código que da problemas y a correr :D. La JVM de Sun usada es la 1.4.2_03.

El test es casero, sin criterio y simplemente a ver que pasa. Espero que nadie de más valor a este texto del que tiene.

Vamos a ejecutar un programita test.fth tal que:

\ factorial
: FAC
        DUP
        1 > IF
                DUP 1 - FAC *
        ELSE
                DROP 1
        THEN
;

."Procesando..." CR
10 FAC ."Resultado: " . CR
10000 0 DO
        10 FAC DROP
LOOP

Consta de una función FAC que calcula el factorial de un número dado de forma recursiva. El programa muestra un mensaje y el resultado del factorial de 10 (para comprobar que factoriza bien). Después realiza esa misma operación 10000 (diez mil) veces.

Tantas iteraciones es para que tarde más y me de tiempo a valorar diferencias.

Primera ejecución: JVM de Sun.

$ javac *.java
$ time java clSfi < test.fth
Procesando...
Resultado: 3628800

real    0m1.869s
user    0m1.800s
sys     0m0.030s

Segunda ejecución: gcj de GNU.

$ gcj-3.0 -O2 -march=i686 *.java --main=clSfi -o native
$ time ./native < test.fth
Procesando...
Resultado: 3628800

real    0m2.965s
user    0m2.960s
sys     0m0.000s

Notar que en gcj he empleado optimizaciones y he generado código para i686. De esta forma he querido exprimir al máximo las posibilidades del código nativo generado. Con la JVM de Sun no se puede optimizar nada, siempre es bytecode para interpretar.

El resultado es que, en este caso, el código nativo es más lento, además de que hemos tenido que modificar el intérprete para que compilara (insisto que es debido, probablemente, a la versión algo vieja que viene con Debian estable).

En fin, tampoco sabía que resultados esperar.

A última hora he decidido hacer una prueba extra: ¿como llevarán ambos la recursividad de FAC para calcular el factorial de 10000?

Está claro que no nos mostrará el resultado porque sfi emplea enteros y no cabrá un número tan grande. A ver que pasa :D.

$ time java clSfi < test.fth
Exception in thread "main" java.lang.StackOverflowError

real    0m0.479s
user    0m0.170s
sys     0m0.030s

$ time ./native < test.fth
Violación de segmento

real    0m0.202s
user    0m0.040s
sys     0m0.010s

¡Sorpresa! El resultado es aparentemente el mismo, pero no es cierto.

En un caso la JVM (Java Virtual Machine, o máquina virtual Java) captura la excepción por pasarnos de rosca con la pila.

En el otro es el sistema operativo el que captura el error, mucho más serio que en el caso de la máquina virtual. Esto da que pensar.

¿Las conclusiones? Difíciles. No he usado una versión de gcj que cumpla eso de state of the art y que es más bien vieja. Además la prueba no va orientada a ningún test tipo, sino a ejecutar un script en mi intérprete.

Tampoco he valorado consumos de memoria, que imagino que la JVM de Sun tendrá más coste en la ejecución. Pero solo eso, imagino.

Ahora bien, la última prueba de la pila sí tiene moraleja. Un programa nativo es eso nativo. No hay una máquina virtual que capture "nuestros" fallos. En cuanto a seguridad hemos perdido enteros al correr el programa de forma nativa.

Así que creo que si el compilador a código nativo no consigue reproducir el 100% del API de Java, que podría ser una ventaja respecto a otros lenguajes compilados, no veo motivos por los cuales usar Java sacrificando sus posibilidades en cuanto a portabilidad y seguridad (olvidando premeditadamente el tema de la velocidad, que no he podido certificar con mi versión de gcj).

Había olvidado lo entretenido que es hacer comparativas ;).

Anotación por Juan J. Martínez.

Hay 2 comentarios

Gravatar

Yo también estuve trasteando una temporada con el gcj y no pude llegar a ninguna conclusión clara en cuanto a la velocidad. En general pude ver que para aplicaciones muy pequeñas con gcj van las cosas un pelín más rápido, pero para aplicaciones grandes mejor ejecutar sobre JVM.

por ToReK, en 2004-05-13 10:15:07

Gravatar

Del artículo de Libertonia se desprende que el gcj no es más rápido en un 100% de los casos. Además el trabajo del intérprete tiene asociado un gran número de creaciones/destrucciones de objetos, que según el citado artículo es uno de los procesos con más coste.

No obstante el sentido común llama a emplear la última versión estable de todo soft para hacer pruebas, y yo no lo he hecho :P

por Juanjo, en 2004-05-13 12:12:52

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.