35. Python y Rendimiento#

Python es un lenguaje interpretado de alto nivel que es muy conveniente para prototipar y hacer análisis exploratorio. Sin embargo, esta conveniencia tiene un costo:

Python tiene un menor rendimiento a igual complejidad en comparación a lenguajes compilados de bajo nivel

Podemos ser más específicos y hablar de eficiencia de diversas índoles:

  • Eficiencia temporal: Tiempo para completar una tarea (tiempo en la CPU)

  • Eficiencia espacial: Utilización de espacio (memoria RAM, disco)

Ambos son factores críticos en algunas aplicaciones, por ejemplo aplicaciones con muchos datos o mucho cómputo.

Existe entonces una necesidad por mejorar el rendimiento de nuestro código. En esta serie de lecciones veremos distintas formas de optimizar código escrito en Python.

35.1. ¿Qué es la optimización de códigos/software?#

Optimización se refiere a modificar una rutina computacional para mejorar su eficiencia, es decir reducir sus tiempos de ejecución y/o consumo de recursos. El aspecto que se intenta modificar es aquel que limita nuestro programa. Un programa puede estar:

  • limitado en CPU: compute-bound

  • limitado en memoria: memory-bound

  • limitado en ancho de banda: bandwidth-bound

  • limitado en entrada/salida: I/O bound

En el ámbito de la computación científica lo más común es enfrentar programas que están límitados en CPU, es decir:

  • programas que utilizan la mayor parte de su tiempo haciendo cálculos

  • programas que mejoran su rendimiento con la velocidad del CPU

Nota

En ciertos casos podemos disminuir el tiempo de ejecución de una rutina incrementando el uso de memoria. Esto podría convertir un programa que es limitado en CPU a uno que es limitado en memoria.

35.2. Antes de optimizar#

Considera las siguientes preguntas antes de comenzar el (a veces arduo) proceso de optimizar tus códigos.

¿Cuándo optimizar?

Si:

tu rutina está incompleta o no entrega el resultado esperado

Entonces:

No es momento de optimizar 

Para casi cualquier rutina que escribamos, deberíamos considerar la secuencia:

  1. que (la rutina) corra

  2. que (la rutina) retorne el resultado correcto

  3. (opcionalmente) que (la rutina) tenga un buen rendimiento

Importante

La optimización está asociada al último punto y se lleva a cabo luego de cumplir los dos primeros

En la práctica hay que considerar que optimizar podría:

  • Hacer el código más complicado y menos legible

  • Introducir bugs

  • Tomar tiempo y bastante dedicación

Por lo tanto debemos evitar optimizar de forma prematura

La optimización prematura es la raíz de todos los males

Donald Knuth

¿Por qué optimizar?

En la secuencia mostrada anteriormente, podemos notar que el último punto no es esencial como lo son los dos primeros. Optimizar solo es necesario si nuestro código:

  • no entrega el resultado correcto en el tiempo requerido

  • requiere más memoria que la disponible en el hardware donde va a correr

¿Dónde optimizar?

Se debe evitar gastar tiempo optimizando rutinas que influyan poco en el rendimiento total del programa. La optimización debería concentrarse en las secciones más lentas y/o costosas: cuellos de botella

Previo a optimizar debemos hacer un perfilado (profiling) de nuestro código para encontrar las secciones críticas. Veremos como hacer perfilado con ipython a continuación.