Optimización de código: un código más eficiente

2 mayo, 2016
Foto: John Schnobrich en Unsplash.

En los primeros tiempos de la informática, programar exigía utilizar lenguajes muy próximos a la plataforma de ejecución. El código máquina o el lenguaje ensamblador permiten un control muy fino del proceso de ejecución: cómo se asignan los registros del procesador, cómo se almacenan los datos y se accede a la memoria, … Así es posible exprimir la máxima eficiencia de una plataforma, aunque el precio a pagar es la falta de portabilidad y el mayor coste de desarrollo y mantenimiento del código.

Afortunadamente, hoy en día podemos utilizar lenguajes de programación de alto nivel, que abstraen estos detalles de la plataforma y nos permiten ser más productivos resolviendo problemas más complejos en menos tiempo. Sin embargo, con esta transición hemos perdido algo de eficiencia… ¿o no? Quizás tampoco tanta, porque los compiladores incorporan técnicas que permiten mejorar el rendimiento del código generado.

Ejemplo de optimización
Ejemplo de una optimización de bucles: la variable i sólo se utiliza para calcular la variable t. Modificando el código podemos actualizar el valor de t en cada iteración, ahorrándonos una multiplicación.

La optimización de código es el conjunto de fases de un compilador que transforman un fragmento de código en otro fragmento con un comportamiento equivalente y que se ejecuta de forma más eficiente, es decir, usando menos recursos de cálculo como memoria o tiempo de ejecución. Es importante destacar que:

-La condición de tener un «comportamiento equivalente» es bastante onerosa, ya que incluye también situaciones de error donde el comportamiento debe ser el mismo. Pongamos como ejemplo una instrucción como «x = y / y«. La tentación es substituir esta expresión por «x =1«, pero es necesario garantizar que la variable y no puede ser igual a 0, porque entonces el código podría tener un comportamiento diferente según el lenguaje (p.ej. división por cero).

-También es fundamental garantizar que el código no será menos eficiente que antes de optimizarlo. Por ejemplo, el desenrollado de bucles (loop unrolling, replicar N veces el código de un bucle) puede reducir el tiempo de ejecución al simplificar instrucciones innecesarias (p.ej. saltos), aunque al hacerlo puede hacer crecer el tamaño del código. Dado que el tamaño del código puede tener consecuencias a nivel de acceso a caché y a la memoria, podría pasar que el programa optimizado acabara siendo más lento que el original.

Existen diferentes técnicas para optimizar el código, cada una de las cuales intenta mejorar un aspecto diferenciado del código. En general pueden clasificarse en dos categorías, las de flujo de datos (data-flow) y las de flujo de control (control-flow). Las optimizaciones del flujo de datos pretenden mejorar la eficiencia de los diferentes cálculos realizados en el programa: precalculando expresiones con valor conocido en tiempo de compilación, reaprovechando cálculos ya realizados en otras partes del código o suprimiendo cálculos innecesarios, …  Por contra, las optimizaciones del flujo de control intentan utilizar las instrucciones de salto condicional e incondicional de la forma más eficiente posible (ya sea desplazando código o eliminando saltos innecesarios). Puede definirse también una tercera categoría, las optimizaciones de bucles (loop optimization), intenta mejorar el rendimiento de  las instrucciones iterativas como for, while … dorepeat … until. En este caso, los cambios realizados al código del bucle afectan tanto al flujo de datos como al flujo de control.

La ventaja de todas estas técnicas es que se aplican de forma automática cada vez que compilamos nuestro código, de forma que podemos centrarnos en hacer nuestro código legible en lugar de intentar optimizar «a mano». Como dijo Donald Knuth, «la optimización prematura es la raíz de todos los males». Dejemos a los compiladores los detalles de bajo nivel y dediquémonos a diseñar  un buen algoritmo, que ya es un trabajo suficientemente complejo.

Para saber más: si estáis interesados en conocer más detalles sobre el proceso de optimización de código en un compilador, tenéis más información en este enlace. También os recomiendo el libro Modern Compiler Implementation de Andrew W. Appel.

(Visited 3.339 times, 4 visits today)
Autor / Autora
Robert Clarisó Viladrosa
Comentarios
Deja un comentario