javascript eficiente
Más rápido
Menos memoria
Más ligero
Para optimizar pregúntate:
- ¿Qué podrías hacer de forma más eficiente con tu código?
- ¿Cuáles son las optimizaciones comunes de los motores de JavaScript?
- ¿Qué es lo que el motor de JavaScript NO puede optimizar?
- ¿Está el recolector de basura (GC) limpiando lo que esperas que limpie?
Hay que saber como funcionan los motores de javascript
- V8 en Chrome
- SpiderMonkey en Firefox
-
Chakra en Internet Explorer
- Carakan en Opera
Hay que optimizar pensando en todos.
Pero analicemos el más documentado: V8
Pero analicemos el más documentado: V8
Analicemos el más documentado
Hay que entender los principales conceptos del motor.
Las piezas de V8
Las piezas fundamentales son las siguientes:
- Base Compiler o compilador base
- Object model o modelo de objetos
- Runtime profiler
- Optimizing Compiler o compilador optimizador
- Desoptimizaciones
- Garbage Collector (GC) o recolector de basura
Base compiler
- Parsea el código javascript
- Genera código nativo
antes de ejecutarlo
El código inicial no está
especialmente optimizado porque
el objetivo es compilarlo lo más rápido posible
el objetivo es compilarlo lo más rápido posible
Object Model
En JavaScript los objetos son arrays asociativos.
En V8 se representan internamente
con clases ocultas que aceleran
el acceso a propiedades.
con clases ocultas que aceleran
el acceso a propiedades.
Runtime Profiler
Monitoriza constantemente
la aplicación que se está ejecutando
identificando las "funciones calientes"
la aplicación que se está ejecutando
identificando las "funciones calientes"
O sea, las que se ejecutan la mayor
parte del tiempo.
Optimizing compiler
Optimiza y recompila el código caliente
localizado por el Runtime Profiler.
Un ejemplo de optimización es el inlining:
reemplazar la llamada a una función
por su cuerpo
por su cuerpo
Desoptimizaciones
El compilador puede desechar optimizaciones
volviendo a la versión inicialmente compilada.
Esto ocurre cuando las hipótesis que se hicieron
para optimizar dejaron de ser válidas.
Garbage collector
IMPORTANTE,
MUY IMPORTANTE,
IMPORTANTÍSIMO
ENTENDER CÓMO FUNCIONA
Garbage collector
Hablamos de gestión de memoria:
intenta recuperar y limpiar la memoria
ocupada por objetos que ya no se usan.
Mientras los objetos estén referenciados no se limpian.
¿Pero hay que estar pendiente de la gestión de memoria?
Habitualmente no.
No hace falta referenciar manualmente.
Pon las variables donde las necesites,
lo más local posible y ya está.
Errores habituales
La instrucción delete elimina una clave de un hashmap pero no fuerza la liberación de memoria.
var o = { x: 1 };
delete o.x; // true
o.x; // undefined
Esto convierte a
o
en un objeto "lento".
Mejor usar null que delete.
NULL tampoco elimina objetos
var o = { x: 1 };
o = null;
o; // null
o.x // TypeError
Al asignar null, no eliminamos al objeto sino la referencia.
Solo si es la última referencia, el objeto podrá ser limpiado por el GC (cuando este lo decida).
Variables globales
Como poniendo
var myGlobalNamespace = {};
¡NUNCA SE LIMPIAN!
¡Solo se libera la memoria refrescando la página!
Regla de oro
Para que el Garbage Collector pueda recolectar el máximo número de objetos lo más pronto posible
NO REFERENCIES A OBJETOS
QUE YA NO NECESITES
QUE YA NO NECESITES
- Pon las variables en el scope adecuado (el más
interior posible) - Unbind de eventos que ya no se necesitan
(¿desapareció el DOM del evento?) - ¿Cacheas datos locales?
Controla el descacheo y elimina lo viejo
Funciones
Compara esto:
function foo() {
var bar = new LargeObject();
bar.someCall();
}
con esto:
function foo() {
var bar = new LargeObject();
bar.someCall();
return bar;
}
// somewhere else
var b = foo();
Closures
Las función interna tiene acceso al scope exterior incluso después de que la función sea ejecutada:
function sum (x) {
function sumIt(y) {
return x + y;
};
return sumIt;
}
// Usage
var sumA = sum(4);
var sumB = sumA(3);
console.log(sumB); // Returns 7
CLOSURES II
Compara
var a = function () {
var largeStr = new Array(1000000).join('x');
return function () {
return largeStr;
};
}();
con esto
var a = function () {
var smallStr = 'x';
var largeStr = new Array(1000000).join('x');
return function (n) {
return smallStr;
};
}();
TIMERS
Alguno del equipo
ya está escarmentado de esto ;)
ya está escarmentado de esto ;)
Ojo con los setTimeout y setInterval
TIMERS
Mira atentamente este código
var myObj = {
callMeMaybe: function () {
var myRef = this;
var val = setTimeout(function () {
console.log('Time is running out!');
myRef.callMeMaybe();
}, 1000);
}
};
Ahora ejecuta
myObj.callMeMaybe();
Observa la consola y ejecuta después
myObj = null
Trampas que afectan
al rendimiento
Recuerda: nunca optimices hasta que lo necesites.
Mide el efecto de tus optimizaciones,
igual la mejora es imperceptible.
igual la mejora es imperceptible.
Ejemplo
Supongamos que hay que crear un módulo que:
- Utiliza una fuente local de datos
con elementos con un id numérico - Pinta una tabla con estos ids
- Añade eventos para alternar
una clase al hacer click
en cualquier celda
Cuestiones
- ¿Cómo almacenamos los datos?
- ¿Cómo escribimos una tabla
eficientemente y la adjuntamos al DOM? - ¿Cómo organizamos los eventos
de una forma óptimo?
1ª Aproximación
- Guardamos los datos en un objeto
- Almacenamos los objetos en un array
- Con jQuery iteramos sobre los datos,
pintando la tabla y la adjuntamos
al DOM - Finalmente, enganchamos los eventos
para gestionar los estilos.
1ª Optimización
- Si utilizamos solo los ids, en el array
interesa solo guardar los ids - Usar DocumentFragment y metodos
nativos de DOM es más rápido (jQuery
utiliza internamente DocumentFragment) - Binding común de click, en lugar de a cada
td individual.
Midámos las mejoras: http://jsperf.com/first-pass
2ª Optimización
Se suele comentar en internet
que el patrón de prototype es mejor
que el patrón de módulo.
que el patrón de prototype es mejor
que el patrón de módulo.
Y además, que hay super herramientas de templating
Optimizamos: https://gist.github.com/4710559
Y las mejoras de rendimiento resultan ser menos importantes:
Consejos para optimizar v8
- Hay patrones de código que deshacen
optimizaciones como el try-catch. - Procura que tus funciones sean
monomórficas, es decir, que las variables
siempre sean de la misma clase oculta.
NO HAGAS ESTO:
function add(x, y) {
return x+y;
}
add(1, 2);
add('a','b');
add(my_custom_object, undefined);
Más consejos
- No escribas funciones enormes,
son más difíciles de optimizar - Cuidado con elementos no inicializados
a = new Array();
for (var b = 0; b < 10; b++) {
a[0] |= b; // Oh no!
}
//vs.
a = new Array();
a[0] = 0;
for (var b = 0; b < 10; b++) {
a[0] |= b; // Much better! 2x faster.
}
Para cuando tengas un rato
Google I/O 2012 -
Breaking the JavaScript Speed Limit with V8
Breaking the JavaScript Speed Limit with V8
Optimizing for V8 - A series
Objetos vs Arrays
- Si almacenas un listado de números
o de objetos del MISMO TIPO, usa un array - Si semánticamente necesitas un objeto con
muchas propiedades, usa un objeto, será rápido - Las propiedades indexados por un número son
mucho más rápidas en array u objeto que las
propiedades normales
http://jsperf.com/performance-of-array-vs-object/3
- Las propiedades son más complicadas que
los accesos a elementos de un array, y por tanto
se pueden hacer menos optimizaciones.
Try Catch
El try...catch impide optimizar
el código que tiene dentro.
el código que tiene dentro.
Además, si haces caso a Uncle Bob, una función
con try...catch no debe tener más responsabilidades.
con try...catch no debe tener más responsabilidades.
function perf_sensitive() {
// Resuelve aqui la funcionalidad sensible al rendimiento
}
try {
perf_sensitive()
} catch (e) {
// Gestiona la excepcion
}
Usando objetos
- Crear objetos con una función de construcción
es más eficiente porque todos tienen la misma
clase oculta - No hay límite al número de objetos diferentes
o a su complejidad, pero... - En los objetos calientes, intenta mantener
las cadenas de prototipos (prototype chains)
cortas y un bajo número de propiedades.
CLonando objetos
- Cuidado con los bucles for...in, nunca serán
rápidos - Lo más eficiente siempre será usar
una función constructora (para usar en
código caliente):
function clone(original) {
this.foo = original.foo;
this.bar = original.bar;
}
var copy = new clone(original);
Funciones cacheadas
Para usar dentro del patrón de módulo.
Test rendimiento: http://jsperf.com/prototypal-performance/12
USANDO ARRAYS
No borres elementos del array.
Internamente pasa a un modelo más lento.
Cuando las claves son escasas, eventualmente
pasa a un modo hashmap mucho más lento.
pasa a un modo hashmap mucho más lento.
Array literals
Dan una pista al compilador del tipo y tamaño
de los arrays.
de los arrays.
// Aquí V8 ve que quieres un array de 4 elementos con números:
var a = [1, 2, 3, 4];
// No hagas esto:
a = []; // V8 no sabe nada del array
for(var i = 1; i <= 4; i++) {
a.push(i);
}
Tipos simples o mezclados
No es buena idea mezclar distintos tipos
en el mismo array:
booleanos, enteros, undefined, textos, ...
en el mismo array:
booleanos, enteros, undefined, textos, ...
Arrays dispersos o completos
Cuando el array no está completo, V8 empieza
a usar un diccionario que consume
menos recursos pero es más lento.
a usar un diccionario que consume
menos recursos pero es más lento.
Arrays compactos o COn HUECOS
Evita los "agujeros" en un array.
Con que solo haya un hueco ya será más lento.
Preasignar o crecer
Antes que reservar un espacio grande (por ej, 64k)
para un array, suele ser mejor crecer a medida que se necesite (aunque depende del VM), porque al reservar
apenas ganamos tiempo.
para un array, suele ser mejor crecer a medida que se necesite (aunque depende del VM), porque al reservar
apenas ganamos tiempo.
// Empty array
var arr = [];
for (var i = 0; i < 1000000; i++) {
arr[i] = i;
}
// Pre-allocated array
var arr = new Array(1000000);
for (var i = 0; i < 1000000; i++) {
arr[i] = i;
}
OPTIMIZANDO TU APLICACIÓN
La velocidad lo es todo
Los tres pasos:
- Mide: encuentra los puntos
lentos de tu aplicación (≈45%) - Comprende: averigua cual es
problema real (≈45%) - Arregla. (≈10%)
Benchmark
Mide el rendimiento de tus aplicaciones.
Por ejemplo, comparando tiempos:
Ten en cuenta que esto no es 100% fiable.
Por ejemplo, el GC puede afectar las pruebas.
Por ejemplo, el GC puede afectar las pruebas.
Más detalles sobre benchmarks:
Profiling
El perfilador de Chrome te permite localizar
las funciones que más tiempo consumen:
las funciones que más tiempo consumen:
Profiling
Puedes medir uso de CPU, rendimiento
de los selectores de CSS y el uso de memoria.
de los selectores de CSS y el uso de memoria.
Para leer: Como hacer profiling (en inglés)
Memoria
Creencia habitual:
Más memoria == Mejor rendimientoRealidad:
más memoria implica más latencia y variabilidad
causas de memory leaks
Las causas más habituales suelen ser:
- Event Listeners.
- Caches locales
- Almacenamiento en objetos en arrays y hashes
que nunca se limpian - Timers
evolución de memoria
Con la papelera puedes forzar el GC
Memory leaks
Nos apoyamos en las herramientas de Devs
Haz varias tareas, se fuerza el GC y mira el número de nodos DOM. Si no baja hay un problema.
Toca aplicar la técnica de los Tres SnapShots
Técnica 3 snapshots
¿Por qué funciona?
- Cuando das a "Take SnapShot", fuerza al GC y luego toma una foto
- #1 representa una línea base
- Los nodos amarillos en #2 son los nuevos añadidos por las acciones realizadas.
- Las líneas punteadas representan los objetos asignados por las acciones correctamente tratados por el GC antes del snapshot.
- Hasta este punto, resulta dificil determinar si la memoria asignada es un leak o está intencionadamente dejada ahi (por ej, caches)
- Por lo tanto, realizamos las mismas tareas tomando un tercer snapshot.
- Tras el 3er snapshot, seleccionamos el "Summary" del Snapshot 3 y filtramos para que
muestre solo los objetos que han sido asignados entre el 1 y el 2. Ahora tenemos claros
que los objetos alojados entre Snapshots 1 y 2 no deberían estar ahi, por lo que
los objetosmostrados son potenciales leaks.
Localizando memory leaks
¿Qué hacer entonces?
- Examina el camino que retiene a los objetos perdidos
- Si no se puede inferir fácilmente las referencias (p. ej. event listeners):
- instrumenta el constructor de los objetos mediante la consola para identificar mejor el stack trace
- ejecuta el escenario de nuevo
- Arréglalo:
- Desecha correctamente los objetos basura
- unlisten() en event listeners
La foto final:
REFLOWS
Reflow: cuando un navegador tiene que
recalcular las posiciones y dimensiones
recalcular las posiciones y dimensiones
de un elemento en un documento para repintarlo
El reflow bloquea el navegador
frente a interacción del usuario
por lo que es importante entenderlo
para mejorar el tiempo de reflow.
frente a interacción del usuario
por lo que es importante entenderlo
para mejorar el tiempo de reflow.
Reflows
Mejorando reflows
- Batch the metodos que lanzan reflows o repintan
- Mejorar procesar elementos fuera del DOM.
Usando un DocumentFragment, por ejemplo. -
Por ejemplo, ¿cual es el problema del siguiente
código?
function addDivs(element) {
var div;
for (var i = 0; i < 20; i ++) {
div = document.createElement('div');
div.innerHTML = 'Heya!';
element.appendChild(div);
}
}
Evitamos el reflow
Utilizamos un DocumentFragment y luego adjuntamos
al DOM
al DOM
function addDivs(element) {
var div;
// Creates a new empty DocumentFragment.
var fragment = document.createDocumentFragment();
for (var i = 0; i < 20; i ++) {
div = document.createElement('a');
div.innerHTML = 'Heya!';
fragment.appendChild(div);
}
element.appendChild(fragment);
}
CAUSAS DE REFLOW
Hay que minimizarlas en la medida de lo posible:
- Redimensionar la pantalla
- Cambiar la fuente
- Agregar o quitar una hoja de estilos
- Cambios en contenidos (como al escribir en un input)
- Activación de psudo clases CSS como :hover
- Manipulación del atributo class
- Un script que manipule el DOM
- Cálculos de medidas: offsetWidth y offsetHeight
- Cambiar una propiedad del atributo style
CONCLUSION
Mide
Entiende
Arregla
Repite
Fuentes
- Addy Osmany: Writing fast, memory efficient JavaScript
- MDN: Reflows
- Google I/O: Breaking the JavaScript Speed Limit with V8
- Eliminating Memory Leaks in Gmail por Loreena Lee y Robert Hund
- Speeding up with JavaScript: working with DOM
javascript eficiente
By Gonzalo Ruiz de Villa
javascript eficiente
- 42,612