Contenido

Rendimiento extremo de Javascript

10 nov

+ 10

En uno de esos momento de querer pulir scripts en Javascript me pegó la neura de probar diferente formas de hacer lo mismo sacando el cronómetro y midiendo tiempos para ver que era más eficiente y que lo era menos.

Al final, por esas cosas del destino, me tuve que ir al final terminé olvidándome de lo que quería hacer. Hasta ahora que me he encontrado con esta presentación de Thomas Fuchs (author de Script.aculo.us). En ella (la recomiendo) hace exáctamente lo que yo quería hacer, medir los tiempos de ciertas tareas cotidianas y compararlas con alternativas que nos ofrece el mismo resultado.

Algunos de los resultados muestran cosas realmente interesantes:

Rendimiento

Objetos literales frente a clásicos

// Más lento
function literals(){
 var a = [], o = {};
}
// Más rápido
function classic(){
 var a new Array, o = new Object:
}

En la presentación se ven los resultados sobre los diferentes navegadores y podemos ver como de usar uno u otro en Google Chrome podemos multiplicar por 2 el tiempo de proceso.

Loops

Los loops tambien muestran unos resultados curiosos:

var test = '';
for (var i = 0; i<10000; i++)
 test = test + str;

var test = '', i = 10000;
while(i--) test = test + str;

Entre los dos anteriores, no se muestran diferencias en los resultados devueltos.

// Más lenta
function normalLoop(){
 var i = 60, j = 0;
 while(i--) j++;
}

// Más rápida
function unrolledLoop(){
 var j = 0;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
 j++; j++; j++; j++; j++; j++;
}

Tambien vemos que muy caro hacer un loop, en Firefox multiplicamos por 2 el tiempo y en Google Chrome lo multiplicamos por 5.

Cachear variables globales

// Más rádida
function cache(){
 var w = window, i = 10000;
 while(i--) w.test = 'test';
}

// Más lenta
function nocache(){
 var i = 10000;
 while(i--) window.test = 'test';
}

El cachear variables globables nos ayudará en la mayoría de casos.

Acceder a atributos de un objeto frente a with()

// Más lento
function conwith(){
 var obj = { prop: 'test', str: '' };
 with(obj){
 var i = 10000;
 while(i--) str += prop;
 return str;
 }
}

// Más rápido
function sinwith(){
 var obj = { prop: 'test', str: '' }, i = 10000;
 while(i--) obj.str += obj.prop;
 return obj.str;
}

En los resultados nos muestra que usar with() para acceder a un objeto nos puede penalizar el tiempo de proceso de nuestro Javascript. En el caso de Google Chrome reducimos el tiempo a un 7% del empleado con with().

Pasar a bits frente a parseInt()

// Más lenta
parseInt(12.50);

// Más rápida
~~(1 * "12.5");

Javascript 1.5 integra una serie de operadores a nivel de bit, entre ellos nos encontramos ~(Alt Gr + 4) que representa el NOT a nivel de bit y que en este caso nos ayuda a convertir a entero una cadena como podemos ver en el ejemplo (haciendo un doble NOT para obtener un valor positivo).

Curiosidades del lenguaje

Diferencia entre && y ||

var n = 1;
if (true && (n=2)) ...;
console.log(n);
// --> n = 2
if (true || (n=3)) ...;
console.log(n);
// --> n = 2

Como es lógico, el uso de && evalua las dos condiciones frente a || que si ya se cumple la primera, nos evitamos comprobar la segunda.

(...).toString()

(function(){ return 2 * 3; }).toString();

// IE, Safari y Google Chrome
function(){ return 2 * 3; }

// Firefox
function(){ return 6; }

En este ejemplo vemos como la ejecución de esta función anónima nos muestra un resultado algo extraño en Firefox.

Más info

  1. Extreme Javascript Perfomance (Thomas Fuchs)
  2. JsRocks
  • está muy bueno el resumen, pero hay uno que no me gusta.
    comprar un loop con un supuesto loop desenrollado SEGURO es una tontera, funcionalmente son distintos, y los tiempos obvio son otros, no tenés que chequear la condición del loop en ningún momento, en el primer caso lo tenés que hacer tantas veces como cantidad de loops

  • @totoloco: Si, claro. Aunque creo que Thomas quería ilustrar esos casos en los que por comodidad hacemos un loop en lugar de hacer la operación directa.

    Está claro que evitarnos una comprobación nos permite mejorar el tiempo :D

  • Recomiendo el libro Even Faster Web Sites del amigo Steve Souder. Concretamentente el capítulo “Writing Efficient JavaScript”. Este capítulo lo ha escrito otro ilustre (Nicholas C. Zakas) y trata sobre estas cosas.
    Un saludo.

  • @gonzalo: Gracias por la recomendación. A la cola de libros pendientes :D

  • O.o Me ha sorprendido el comportamiento del toString en una función, en Firefox. toSource retorna lo mismo, además.

  • Interesante lo de cachear las variables globales. Yo llamaba siempre a window. directamente.
    Lo que si, imagino que si la cacheas estas consumiendo mas memoria.

    Lo de “Diferencia entre && y ||” nose a que quiere llegar, a que || es mas rapido porque hace una sola condicion? Si es eso ni hacia falta aclararlo.

  • @Martin: Exacto, esa era la diferencia.

    Por experiencia te puedo asegurar que nunca está de más :D

  • Tal y como lo escribes parece que de igual usar ‘&&‘ que ‘||‘, cuando depende de la lógica del programa que vayas a usar uno u otro.

    Tu has mostrado cómo se dispara la evaluación perezosa (EP, lazy evaluation) en ‘&&‘ y cómo no se dispara con ‘||‘, pero el caso es que tanto el ‘&&‘ como el ‘||‘ hacen EP, sólo que en casos distintos. Te pongo los contraejemplos:

    // disparar EP con &&
    n=1;
    if (false && (n=2))…;
    // n=1
    
    // no disparar EP con ||
    n=1;
    if (false || (n=2))…;
    // n=2
    

    Racionalización: cada operador tiene su lógica (por eso son operadores lógicos :p):
    – en el caso de ‘&&‘: todas las condiciones deben ser ciertas, con lo que se detendrá en la primera que sea falsa, y continuará evaluando mientras lo que encuentre sea cierto;
    – en el caso de ‘||‘: con que haya al menos 1 caso que sea cierto le vale, con lo que se detendrá precisamente en el primero que encuentre que sea cierto, y continuará evaluando mientras lo que encuentre sea falso.

    El truco para aprovecharse de esto sería cuando tienes varias (o muy costosas) condiciones en los paréntesis del ‘if‘, dependiendo de si lo que te interesa es ‘&&‘ u ‘||‘ buscar la que sea más probable o rápida de que evalúe al valor que dispara la EP para cada caso y poner ese el primero. Por ejemplo si en el ‘if‘ llamas una función que tiene algún bucle dentro, pues eso que te ahorras.

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.