2011/03/12

Optimización de javascript (VII). Escribiendo código óptimo

Todos (entre los que me incluyo) por desconocimiento o vagancia, hacemos código que podía ser mejor pero que por pereza u otras escusas no tan importantes queda un tanto "descuidado" usando palabras suaves.

Buscando por "ahí" he visto varias perlas que pese a ser buenas de por sí, están desperdigadas entre mucha paja. Voy a intentar recopilarlas todas en estos posts.

Partiendo de la premisa de que el código más óptimo es el que no se ejecuta, vemos que en algoritmos de orden 1 optimizar código puede ser un poco tonto, pero cuando subimos de orden como n o incluso n^2 y no digamos n^3 (o superiores, so bestias! qué estáis haciendo?!?!?) cada ciclo de reloj cuenta. Al turrón!

Reducción del ámbito de ejecución
Dentro de cada bloque se ven tanto las variables definidas dentro de ese bloque como las de todos sus padres, de modo que mientras más saltos de nivel de profundidad en el acceso a variables dentro del ámbito, más se tarda en ejecutar el código. Para reducir los saltos podemos echar mano de estas técnicas:
  • Almacenar en variables locales las variables que se encuentren fuera del ámbito actual. Especialmente con variables globales.
  • Evitar el uso de WITH ya que incrementa en uno el nivel de profundidad del ámbito. Usar variables locales en vez de WITH.
  • Cuidado con TRY-CATCH. CATCH incrementa en uno el nivel de profundidad del ámbito.
  • Usar las closures con moderación. Si no sabes qué es una closure, ahora es el momento. Si aun así te has quedado igual, piensa en que son funciones auto-ejecutadas. En jQuery se usan continuamente y son las típicas que van dentro de las llamadas: blahblahblah( function() { ...algo...} )
  • No olvidar usar VAR al definir las variables. Si no usas VAR para definir variables estás cometiendo un epic-fail porque todas tus variables serán globales!. Al próximo que se le olvide usar VAR, le corto las manos.
Acceso a datos
Se puede accceder a la información de 4 maneras:
  1. Literal. var nombre = “pepe”;
  2. Variable. var nombre2 = nombre;
  3. Propiedad de objeto. var nombre3 = objeto.nombre;
  4. Elemento de un array. var nombre4 = items[0];
Conclusiones después de controlar el tiempo usado en cada tipo de acceso a la información:
  • El método más rápido es el 1. 
  • La diferencia en tiempo entre el 1 y el 2 es despreciable (si la variable es local, ojo).
  • Los métodos 3 y 4 son más costosos y depende del navegador en cuánto lo son (seguro que en IE6 es de orden n-factorial).
  • Mientras mayor sea el nivel de profundidad, más costoso. coste de objeto.nombre.name >> objeto.nombre.
  • No hay diferencia entre objeto.nombre y objeto[“name”] salvo en safari que la notación por punto es más rápida.
Recomendaciones
  • Almacenar los casos 3 y 4 en variables locales si:
  • La propiedad de un objeto se accede más de 1 vez.
  • El elemento del array se accede más de 1 vez.
  • Minimizar el nivel de profundidad para acceder a los datos en el objto/array.
Ejemplo:

function procesa(datos) {
    if(datos.count > 0) {
        for(var i=0; i<datos.length; i++) {
            procesaDatos(elemento[i]);
        }
    }
}
Quedaría como:

function procesa(datos) { 
    var cuenta = datos.length, 
    elemento = datos.element; 
    if(cuenta > 0) {
        for(var i=0; i<cuenta; i++) {
            procesaDatos(elemento[i]);  
        } 
    }
}


Sorprendentemente, la segunda función se ejecuta hasta un 33% más rápido.

Bucles
Aquí es donde se nos escapa el mojo, así que dependiendo de la versión de javascript con la que estemos trabajando (espero por vuestro bien que sea la última y tengáis un navegador decente... sí, Internet Explorer no está en la lista de admitidos, lo sentimos). Ya sabéis, si solo se ejecuta el código una vez (o pocas iteraciones) y no nos pasamos de los 50ms, como que no importa demasiado, pero es bueno saber en qué afectan las distintas estructuras de bucles.

ECMA-357 javascript 2ª edición:
  • FOR-EACH --> Evitar a toda costa!. Muy bonito y legible pero mata el rendimiento.
ECMA-262 javascript 3ª edición:
  • FOR
  • FOR-IN --> Evitar a toda costa!.
  • DO-WHILE
  • WHILE
ECMA-262 javascript 5ª edición:
  • array.forEach --> Evitar a toda costa.
Iteración por funciones
  • jQuery.each(), Y.each(), $each, Enumerable.each() --> Evitar a toda costa. Tardan 8x veces más.
El uso de FOR, DO-WHILE y WHILE es indistinto a nivel de rendimiento. Solo influye la cantidad de trabajo hecha en cada iteración y el número de iteraciones por lo que para optimizar tiempos habría que reducir una u otra o las dos.

Soluciones sencillas:
  • Almacenar la longitud en una variable local para que no se chequee cada iteración.
  • Incrementar el contador a la vez que se accede (dato = datos[i++])
  • Eliminar acceso a objetos/propiedades usando variables locales para almacenarlos.
  • Usar bucles del tipo (50% más rápidos): for(i = 0, ii = Obj.length; ii--;){}
Hasta aquí con optimizaciones sencillas y resultonas del código.

No hay comentarios:

Publicar un comentario