24 de junio de 2010

Optimización de algoritmos

Una sencilla idea para optimizar un algoritmo (para acelerar su ejecución) es dirigir el flujo chequeando las alternativas más específicas primero (lo menos probable primero), y las más generales después (lo más probable después).

Aún cuando la lógica fuera netamente combinacional, en un algoritmo las condiciones se evalúan secuencialmente, por lo tanto elegir el orden de esa evaluación teniendo en cuenta la probabilidad es fundamental para evitar evaluar repetidamente condiciones que fueran (casi) obvias.

Durante el diseño, por prolijidad e intuición, sin embargo, suele (y quizás deba) realizarse al revés, es decir pensar primero en generalidades poco específicas, ya que son útiles para agrupar y una vez probada la lógica puede pasarse a una versión optimizada.

Un ejemplo:

Top-Down (Para diseñar)

Si el siguiente algoritmo se ejecuta 1 vez por hora

if(month == 12)
{
if(day == 31)
{
if(hour == 0)
{
}
}
}
//Se suprimieron los "else" y cualquier código para simplificar la exposición

En un año chequeará el segundo "if", unas 31*24= 744 veces, y chequeará el tercero además 24 veces ( 768 tests )

Bottom-Up (Para optimizar)

Si el siguiente algoritmo se ejecuta 1 vez por hora

if(hour == 0)
{
if(day == 31)
{
if(month == 12)
{
}
}
}

En un año chequeará el segundo "if", unas 365 veces, y chequeará el tercero además 1 sola vez ( 366 tests ).

Con este simple cambio hemos mejorado la eficiencia en más del 100%, es decir, que el segundo algoritmo podría ser el doble de rápido, consumirá la mitad de los recursos computacionales.

No podemos esperar que el compilador resuelva este tipo de optimizaciones porque no conoce la probabilidad de los resultados para cada condición (al menos en los lenguajes/compiladores actuales no permiten especificar tal probabilidad) cosa que podría ser útil.