lunes, 28 de marzo de 2011

Optimización de performance

En estas últimas semanas estuve dedicado a la optimización de la perfomance de una aplicación. Siempre es una actividad divertida, aunque muchas veces el esfuerzo invertido es demasiado en comparación con los resultados obtenidos. Por eso, armé esta lista de cosas a tener en cuenta, en base a la experiencia obtenida en esta y en otras batallas.

1. Medir antes de optimizar

Es muy difícil determinar a priori que componentes u operaciones causan que una aplicación no sea perfomante. Las intuiciones acerca de esto son casi siempre erróneas porque se corresponden con algo eminentemente subjetivo como el tiempo: el usuario o el programador recuerda que una operación le llevo "mucho tiempo" en alguna ocasión y piensa que eso es lo que hay que optimizar. Las mejoras de perfomance son también muy difíciles de ver a ojo, ya que pueden ser practicamente impercertibles.

Para evitar tener este problema, tener una métrica tomada en forma automática que nos permita tener una idea de la realidad y evite el andar adivinando. Hay muchas herramientas para escribir tests que simulen usuarios accediendo a nuestra aplicación, aunque a veces se complica su adaptación a una aplicación en particular o tienen licencias caras. En nuestro caso pudimos lograr scripts que simularan miles de usuarios utilizando simplemente Ruby y Mechanize.

2. No sirve optimizar fuera del cuello de botella

El código es siempre optimizable. Si miramos durante el tiempo suficiente cualquier método, se nos va a ocurrir alguna manera de lograr que sea más eficiente (y si no se nos ocurre nada siempre queda la opción de reescribirlo en C :)). El problema es que si optimizamos código que no es el cuello de botella de nuestra aplicación, nuestros usuarios no van a sentir ninguna mejora de perfomance. Si un proceso gasta el 95% de su tiempo de ejecución en una sola tarea, cualquier optimización en el resto del código va a ser impercertible. Nuevamente, es muy importante medir para poder determinar donde se encuentran estos cuellos de botella. Las herramientas más útiles en este sentido son los profilers, que nos permiten saber cuales de nuestros métodos utilizaron más tiempo de ejecución.

3. En vez de realizar micro optimizaciones, buscar maneras de reutilizar objetos ya calculados

En general, no es fácil encontrar optimizaciones importantes en código razonablemente bueno. Salvo casos muy particulares, el algoritmo que intuitivamente primero se nos ocurre es suficientemente bueno. Esto causa que optimizar estos algoritmos es una tarea con esperanzas de recompensas bastante bajas y al mismo tiempo bastante riesgosa. Por eso muchas veces es más conveniente guardar el resultado de un algoritmo para su posterior reutilización que buscar oportunidades de optimización; después de todo, el código que no se ejecuta siempre es el más rápido... Sin embargo, cuando decidimos utilizar estas técnicas, hay que tener en cuenta que estamos ganando velocidad de ejecución a costa de mayor utilización de memoria y que si este uso de memoria sube demasiado el uso de CPU también se va a disparar, por la mayor necesidad de ejecutar el Garbage collector. También hay que tener en cuenta que los resultados que tenemos cacheados pueden volverse obsoletos.

4. Aunque tengamos problemas de perfomance, la calidad del código es más importante que su eficiencia.

Despues de unas semanas enfocados en la perfomance de una aplicación, es fácil que confundamos las prioridades, así que vale la pena remarcar que la calidad interna del código sigue siendo la prioridad número uno. La principal característica del código de mala calidad es que es muy difícil de modificar, lo cual hace que sea peligroso implementar cambios que mejoren la perfomance.

5. Tener métricas reales de uso (o por lo menos una idea)

Muchas partes de nuestra aplicación se van a utilizar raramente o nunca. Otras partes se van a utilizar pero con muy pocos datos. Otras se van a utilizar en forma batch, sin usuarios esperando su resultado. Todas estas áreas tienen algo en común: no tiene sentido optimizarlas. Para saber en donde nos conviene enfocar nuestros esfuerzos, es necesario tener algún tipo de métrica que nos indique que módulos son los que realmente se utilizan.

6. Reglas de la optimización de código

Hacer que nuestra aplicación ande más rápido es algo que en general nos entusiasma como programadores. De todas maneras, siempre hay que tener en cuenta lo que la sabiduría de las generacion anteriores nos dice al respecto:

"Premature optimization is the root of all evil"
 DonaldKnuth
 "Rules of Optimization:

 Rule 1: Don't do it.
 Rule 2 (for experts only): Don't do it yet."

 Michael Jackson (no, no es ese, es otro Michael Jackson)

No hay comentarios:

Publicar un comentario