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 ;).

![[xml]](/images/xml.gif)
