asm.js

Recuerdo haber leído sobre asm.js, hace como un par de años. Quizás atraído por su nombre, mezclando ensamblador y Javascript. No me pareció nada relevante en su momento, pues además, sólo estaba adoptado en Firefox. Os voy a contar la esencia de asm.js, y vosotros decidiréis.

asm.js es un subconjunto de Javascript, es decir, cualquier navegador que ejecute Javascript, es en principio capaz de ejecutar asm.js. Está claro, aunque de momento no se le vea la utilidad.

Pero si desde hace años, que Javascript ya no es interpretado, y utiliza un compilador JIT, que en tiempo de ejecución convierte el script a código nativo, ¿qué ganamos? Bueno, digamos que al ser un subconjunto del lenguaje, permite que las instrucciones soportadas, puedan analizarse mejor, y por tanto optimizarse el rendimiento de las mismas. En este caso, las optimizaciones extras, si que requieren que el navegador lo soporte. Es decir, si el navegador acepta asm.js, correremos a velocidad extra, mientras que si no, lo haremos a velocidad habitual de Javascript, en realidad, un pelín más despacio, como luego veremos.

asm.js, utilizando construcciones ya existentes en Javascript, palía dos problemas del lenguaje en cuanto a rendimiento. El primero es que Javascript sigue siendo un lenguaje sin tipos de datos. En general, cualquier variable puede almacenar cualquier tipo de dato. ¿Os suenan los variants de Microsoft? Se lleva hablando más de una década sobre una nueva revisión de Javascript con soporte opcional al tipado, como ocurrió entre ActionScript 2 y ActionScript 3. Sería una gran ventaja en cuanto a la reducción de errores, y a la mejora del rendimiento. Pero quizás Javascript dejaría de tener la imagen de lenguaje sencillo y limpio que tiene. Un aspecto que por otro lado, es cada vez menos cierto.

Ya tenemos la primera mejora, en asm.js podemos emular tipos de datos. De manera que si antes escribíamos:

var iCont = 0

En asm.js escribimos algo como:

"use asm";
var iCont = 0|0

El indicativo de use asm, actúa de manera similar al use strict, indicando al navegador, que lo siguiente, debería ser ejecutado como asm.js. La idea es la misma, es un literal que se ignora si no está soportado, así que no se rompe nada. El pipe, es el operador lógico de OR de Javascript de toda la vida. A efectos prácticos no hace nada, cualquier valor, al que se le ejecuta una O lógica sobre cero, sigue resultando el valor original. Pero, ayuda a asm.js a saber que iCont, sólo tendrá valores enteros. Es decir, forzamos el tipado. Esto ya nos da una pista, ahora si, que ese código es mucho más sencillo de utilizar. En cuánto a porque no haberse aprovechado de las anotacionea, mucho más legibles, es algo que desconozco.

El segundo aspecto que eliminamos es la gestión de memoria. Javascript, a diferencia de C++ por ejemplo, tiene un recolector de basura (GC), se encarga de limpiar aquellos recursos que no usemos, sin que el programador tenga que indicarlo explícitamente. Es un mecanismo muy cómodo, pero que causa bastante penalización en cuanto al rendimiento, y también en la consistencia. Un fragmento de tu programa puede ejecutarse una vez en 0,1 segundos, y la siguiente 0,2 segundos si en ese momento el GC estaba actuando en segundo plano. Sin garbage colector, el soporte de asm.js, es también más fácil.

Pues bien, ya hemos conseguido convertir Javascript, en un lenguaje tipado, y sin recolector de basura. Sobre esa base, se puede implementar en el navegador, un mecanismo que denominan AOT (Ahead Of Time). En contraposición al JIT, ese AOT, lo que hace es cuando encuentra código asm.js, convertirlo directamente a código máquina. Podríamos decir, que directamente coge ese Javascript limitado, y lo convierte a ensamblador, de ahí lo de asm.js.

¿Pero eso no es lo que hace el JIT? En efecto, el JIT de Javascript, también coge el código de script, y lo convierte a código nativo. Lo que pasa es que es un trabajo mucho más complicado, pues debe estudiuar flujos de ejecución, discernir tipos de variables, aplicar optimizaciones, y recompilar dinámicamente. asm.js, en principio no optimiza, simplemente transforma.

Se dice que con asm.js, el rendimiento es entorno a la mitad del código escrito en C, y que podemos obtener mejoras de hasta 10x comparado con Javascript normal. Pero lo habitual es entre 1,5x y 3x.

Todo esto, parece demasiado complicado para lo que en realidad queremos. ¿No hubiera sido más fácil poder prescindir del GC y añadir tipado? Pues soy de la opinión que sí, pero esos cambios, aunque mejores, habrían requerido del soporte de los navegadores, y desarrolladores. Y ya vemos que asm.js, puede ejecutarse aunque el navegador no esté optimizado para él.

Lo siguiente, viene relacionado a asm.js, y explica gran parte de su éxito. Tenemos Emscripten, una herramienta que convierte código objeto de CLang a asm.js. Virtualmente puedo generar código Javascript de mis programas en C++, con mayor o menor esfuerzo. Así vemos motores 3D, o librerías como Qt, que han seguido este camino, y ahora funcionan en un navegador.

Decíamos que asm.js no optimiza el código. Ciertamente, con Emscripten, partimos que ha sido el compilador de C/C++, quien ha aplicado esas optimizaciones previamente. Sabemos que un compilador de C maduro, generará un código de excelente calidad. Un compilador de C, puede tomarse minutos, o incluso horas en generar el código de un proyecto, analizar estáticamente los flujos de ejecución, o incluso utilizar instrumentación PGO para conseguirlo. En cambio el JIT de Javascript, apenas dispone de unas décimas de segundo, pocos segundos en el mejor de los casos. Recordemos que compilar se hace sólo una vez. En cambio la compilación JIT, se hace cada vez que se carga una página… ¡Y el usuario está esperando para verla! Por tanto los resultados no pueden ser los mismos.

Librerías como pdf.js o bpgdec, se aprovechan de asm.js para obtener unos rendimientos cercanos al código nativo. A día de hoy, sólamente unos pocos navegadores tienen caminos optimizados para asm.js (Firefox 22 y Edge 13). Otros lo soportan, pero sin implementar AOT (Chrome 22, y Opera 15), y finalmente en otros es experimental y viene desactivado por defecto (IE 11).

La idea de asm.js, no es la de escribir código directamente en ese dialecto, sino utilizar conversores/compiladores para hacerlo. En lo que a mi respecta, admito que el código asm.js es poco legible comparado con Javascript, y al mismo tiempo limitado. Sin embargo, en códigos que requieren un cálculo intensivo, considero que puede valer la pena trabajar directamente con asm.js. Un poco como antaño se usaba el ensamblador inline en programas BASIC, Pascal o C. Una vez tenemos nuestro código escrito, podemos usar las herramientas de depuración habituales de Javascript. Basta con eliminar la directiva “use asm” y poderlo ejecutar y trazar, como si fuera un programa Javascript normal.

asm.js

8 comentarios en “asm.js”

  1. Javier Gutiérrez Chamorro (Guti)

    Muy interesante tu detector de fuego Jesús Perales. Ya nos contarás como mejora con asm.js.

  2. puedo decir que no me gusta? 😀 no me gusta nada estos derroteros que estan tomando los navegadores. Ya, ya se que es el futuro y blablabla, pero un navegador de toda la vida es un visualizador, a este paso convertiremos los navegadores en rads, asi se expllica luego que pesen cincuenta o cien megas y que inyectado codigos o con directivas cualquiera te meta un virus (ya los hay que incluso encriptan los archivos con javascript). ¿Lo proximo que sera? ¿Un ordenador para usar el navegador y otro para el resto?
    Si quiero un interprete asm me instalo asm, si quiero usar un lector pdf me lo instalo yo, ¿a que viene toda esa maraña de meterlo todo en el navegador? Luego es de risa, la mitad de las cosas no funcionan o tienes que hacer galimatias para que sea compatible con unos y con los otros. El resultado: lo que querias ahorrarte se va al traste para intentar meter variaciones segun el tipo de navegador.
    Donde andara mi querido visual basic script…
    Por cierto, que mania teneis los que escribis en c y en java con el tipo de datos, que mas os da, si luego se pierde todo en otras cosas mas irrisorias, en colorines para los formularios o en animaciones graciosas de patos saltando. Deja vivir a los datos guti, que sean lo que quiera y luego ya cambiaran segun los vaya necesitando en el codigo.

  3. Javier Gutiérrez Chamorro (Guti)

    Me viene a la memoria el artículo de aplicaciones web ligeras, donde comentabas.

    Resulta curioso que sea más pesado Chrome con por ejemplo Outlook.com cargado, que la aplicación nativa de escritorio de Outlook. Se suponía que en el navegador corrían aplicaciones ligeras, pero el navegador es cada vez más gordo, de hecho, empieza a ser tan complicado como un sistema operativo.

    Retomando lo que hablábamos de VB, ahora con los tipos de datos, a mi es algo que me gusta. De hecho uso tipos de datos desde los tiempos de DOS con el Turbo BASIC. Recuerdo como cuando usaba VB profesionalmente, me encargaba de tipar todo, y de asegurarme que los números eran longs de 32 bits, más rápidos en Win32, que los Integer.

    La ventaja de ese enfoque, es que si quieres rendimiento y seguiridad, tienes tipos de datos para hacerlo. Si quieres desarrollar rápido, y el rendimiento te da un poco igual, tienes sin tipo (o Variants como los llama VB). Puedes elegir.

    Ahora con Javascript, no puedes elegir, todo es sin tipo (salvo algunos arrays). Por contra en C, todo es con tipo. Digamos que me gusta el enfoque de Visual BASIC, donde el programador puede elegir. Incluso puede elegir si quería o no declarar las variables…

  4. Javier Gutiérrez Chamorro (Guti)

    Exactamente Manuel. Es una plataforma que lleva ya unos años, pero que no se expande demasiado rápido puesto que parece que solamente los desarrolladores de juegos estén interesados en ella. John Resig lo explicaba mucho mejor que yo.

    También, y aunque no esté pensado para eso, nada impide que escribas código directamente con asm.js, de hecho lo hice en Sieve en asm.js (Javascript).

Deja un comentario