Cargar un módulo grande de WebAssembly solía ser secuencial: el navegador descargaba el archivo completo en un ArrayBuffer, lo entregaba al motor de JavaScript y solo entonces comenzaba a compilar. En conexiones rápidas, la red ya no era el cuello de botella; lo era el tiempo de CPU para decodificar y compilar, y todo ello ocurría después de la descarga, con el hilo principal en pausa.

Las soluciones obvias tenían cada una un costo. Un único compilador optimizador producía código rápido pero tardaba en arrancar. Los motores de JavaScript compilaban de forma perezosa en tiempo de ejecución, lo que evitaba trabajo pero mantenía la compilación en el hilo principal y esperaba a que terminara el análisis. Y parecía no haber forma de compilar antes de que llegara el archivo, porque la antigua API solo aceptaba un ArrayBuffer completo.

Firefox 58 atacó ambos extremos. La compilación en streaming introdujo cada fragmento descargado directamente en el motor mediante la API de streaming y, como la sección de código de un módulo wasm precede a sus datos, la compilación podía comenzar mientras aún había megabytes de datos en tránsito. Un nuevo compilador por niveles añadió un nivel base que generaba código 10-15 veces más rápido que el compilador optimizador, y su resultado se ejecutaba solo unas 2 veces más lento, de modo que el código empezaba a ejecutarse casi de inmediato; después, hilos en segundo plano recompilaban todo el módulo con el optimizador y lo intercambiaban en caliente. Ambos compiladores repartían el trabajo por función entre núcleos.

Como el rendimiento de compilación superaba ahora el rendimiento de la red, el código estaba listo casi en cuanto llegaba el último paquete. Mozilla enmarcó el cambio como hacer que WebAssembly se cargara como decodificar una imagen en lugar de analizar JavaScript: sin pausa de compilación en el hilo principal y sin esperar a la carga completa.

Introducir los bytes en un nivel base rápido a medida que llegan, ejecutar ese código de inmediato e intercambiar en caliente el código optimizado en segundo plano

Compilar el primer paquete oculta el tiempo de descarga dentro del trabajo de CPU en lugar de serializar descarga y luego compilación.

Un nivel base que compila 10-15 veces más rápido convierte el arranque en un inicio rápido; el código aproximadamente 2 veces más lento se ejecuta mientras avanza la optimización.

Como las funciones wasm se compilan de forma independiente, el trabajo se reparte entre núcleos en lugar de hacer cola en un solo hilo.

Poner el código de un módulo antes que sus datos permite compilar en streaming la parte costosa mientras los bytes de la carga aún están llegando.

Cada cambio acorta el arranque sin modificar el código máquina que finalmente se ejecuta, así que el rendimiento máximo no se ve afectado.

Cuando el paso cuello de botella espera la entrada completa, comiénzalo con el primer fragmento y entrega primero una versión barata; actualízala en segundo plano en lugar de bloquear

Firefox 58 incluyó el compilador de streaming y por niveles en enero de 2018; la explicación del lanzamiento generó un hilo de Hacker News con 912 puntos. La página de pruebas de Mozilla permitía a los usuarios medir la velocidad de compilación en sus propios dispositivos, y el equipo dijo que se habían sentado las bases para almacenar en caché el código máquina compilado en la caché HTTP, ampliando la caché de bytecode de JavaScript incluida en la misma versión.

CONSULTA LAS PRUEBAS

Las fuentes

  1. Making WebAssembly even faster: Firefox's new streaming and tiering compiler Mozilla Hacks · hacks.mozilla.org ·
  2. Hacker News discussion: Making WebAssembly even faster Hacker News · news.ycombinator.com ·