Hace casi 6 años que en los artículos sobre Sieve programados en diferentes lenguajes, os mostré la implementación de Sieve en Javascript y de Sieve en Javascript optimizado.

Tanto tiempo es demasiado en tecnología, y mucho más en web. Y aunque a nivel de lenguaje Javascript, no ha habido grandes cambios, es cierto que en 2010, los JIT de Javascript estaban aún en pañales. Así que a raíz del artículo asm.js, me propuse comprobar por mi mismo, como de grande es esa mejora, si sacábamos partido a asm.js.

Recordemos que la versión original de la que partimos era esta:

var ITER=10000;
var flags=Array(ITER);
var i, k, iter, count;
var start, end;
 
start=new Date().getTime();
for(iter=1; iter< =ITER; iter++)
{
	count=0;
	for(i=0; i<=ITER; i++)
	{
		flags[i]=0;
	}
	for(i=2; i<=ITER; i++)
	{
		if (flags[i]==0)	/* found a prime */
		{
			count++;	
			for (k=i+i; k<=ITER; k+=i)
			{
				flags[k]=1;
			}
		}
	}
}
end=new Date().getTime();
alert(ITER + ' iterations, ' + count + ' primes found, ' + (end-start) + ' ms.');

Procedemos a su conversión. Esencialmente lo que hemos hecho es recodificarlo para usar el patrón de módulo, de manera que se aísle la reserva de memoria del array flags fuera del código asm.js. Hemos convertido ese arreglo a uno tipado usando las nuevas características de Javascript. He optado por mantener todos los nombres de variables igual, incluso aquellos que eran constantes ITER, y ahora se usan como argumento al módulo. Una mala práctica, pero que ayudará a seguir el proceso de conversión, o incluso compararlo con los otros lenguajes.

Luego nos hemos asegurado que todas las variables tuvieran tipo explícito a enteros, y así es como ha quedado el programa:

"use strict";
function AsmJs(stdlib, foreign, heap)
{
"use asm";
function Sieve(ITER)
{
var ITER=ITER|0;
var iter, i, k, count;
 
for(iter=1|0; iter< =ITER|0; iter+=1|0)
{
count=0|0;
for(i=0|0; i<=ITER|0; i+=1|0)
{
heap[i|0]=0|0;
}
for(i=2|0; i<=ITER|0; i+=1|0)
{
if (heap[i|0]===0|0)
{
count+=1|0;
for (k=(i+i)|0; k<=ITER; k+=i|0)
{
heap[k|0]=1|0;
}
}
}
}
return(count);
}
return { Sieve: Sieve };
}
 
var start, end, count;
var ITER=10000;
start=new Date().getTime();
var module = AsmJs(window, null, new Int8Array(ITER));
count=module.Sieve(ITER);
end=new Date().getTime();
alert(ITER + ' iterations, ' + count + ' primes found, ' + (end-start) + ' ms.');

Como siempre que se hace una comparativa de este tipo, los resultados son variopintos, y a veces también sorprendentes:

Navegador Tiempo de ejecución asm.js (ms) Tiempo de ejecución Javascript (ms)
Opera 38 (Chromium 51) 3.655 5.943
Firefox 49 (Chromium 51) 3.815 5.073
Edge 24 10.315 6.676
Internet Explorer 11 16.501 11.569

Vemos que Firefox, el principal impulsor de asm.js, queda relegado a la segunda posición, tras Chromium/Opera. Sin embargo, la versión Javascript estádar, es precisante en Firefox donde se ejecuta de manera más veloz. De nuevo irónico con los esfuerzos que ha puesto Google en V8.

Le sigue Edge, que a pesar de haber activado las características experimentales para soportar asm.js, es más rápido en la versión pura de Javascript. Ya habíamos comentado, que los tipados de datos (usando | o +), eran un ligero freno. Internet Explorer, muestra el comportamiento esperado, no brilla en la velocidad de ejecución Javascript, y como no soporta asm.js, pues ésta versión es aún más lenta. El freno de un código asm.js, es en plataformas que no lo soportan de manera nativa del orden de un 40%. La mejora en plataformas que lo soportan va del 30% al 70%, parejo al 1,5x que muchos afirman.

¿Vale la pena el esfuerzo de escribir directamente asm.js? Pues para funciones muy concretas, y en plataformas determinadas, la respuesta es que sí, no podemos desdeñar una mejora de 1,5x, pero en general, es un trabajo extra, que de la misma forma que nos puede aportar beneficios en algunas plataformas, en otras será un freno.

Te dejo el código para revisarlo y ejecutarlo directamente aquí (1 Kb. en formato HTML).