¿Qué es el perfilamiento y por qué es necesario?

El perfilamiento de código nos ayuda a encontrar bottlenecks de nuestro código ya sea en el uso de CPU, RAM, network bandwidth o disco I/O. Una mala práctica que es común en las personas que inician en la programación es intentar optimizar el código (y por optimización piénsese en algún caso, por ejemplo mejorar el tiempo de ejecución de un bloque de código) a ciegas, intentando cambiar líneas de código por intuición y no por evidencias o mediciones. Esto aunque puede funcionar en algunas ocasiones no conduce la mayoría de las veces a corregir los problemas de los bottlenecks del código (o bien en programas o sistemas enteros!). La optimización guiada por la intuición conduce a un mayor tiempo en el desarrollo para un pequeño incremento en el performance:

"Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered" D. Knuth.

"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%". A good programmer ... will be wise to look carefully at the critical code; but only after that code has been identified" D. Knuth.

Y también debe señalarse que las personas programadoras con experiencia también tienen tiempos difíciles identificando bottlenecks en sus códigos...

Si bien es importante que el código resuelva un problema definido, también es importante perfilarlo. Considérese el caso en el que un código resuelve bien un problema en un día completo, es importante entonces perfilarlo para realizar mediciones (CPU, memoria p.ej.) de los lugares en los que el código gasta la mayor parte de tiempo (en general recursos).

A largo plazo el perfilamiento de código te dará las decisiones más pragmáticas posibles con el menor esfuerzo total.

Al perfilar tu código no olvides lo siguiente:

  • Medir el tiempo total de tus códigos para decidir si se requiere optimizarlos.
  • Perfilar tus códigos para decidir en dónde se iniciará con la optimización. También ayuda definir hipótesis para decidir en qué bloques perfilar primero.
  • Escribir tests para asegurarse que se resuelve el problema de forma correcta al igual que antes de perfilarlo y optimizarlo.
  • Cualquier recurso medible puede ser perfilado (no sólo el uso de CPU p.ej.)
  • Perfilar típicamente añade overhead en la ejecución del código (disminución de tiempo de 10x o 100x es común).

En la referencia 1. utilizada para escribir esta nota, se tiene una sección de Strategies to Profile Your Code Successfully en la que se enlistan algunas estrategias para un perfilamiento de código exitoso:

  • Disable TurboBoost in the BIOS (a cool CPU may run the same block of code faster than a hot CPU).

  • Disable the operating system's ability to override the SpeedStep (you will find this in your BIOS if you're allowed to control it).

  • Only use mains power (never battery power) <- a laptop on battery power is likely to more agressively control CPU speed than a laptop on mains power.

  • Disable background tools like backups and Dropbox while running experiments.

  • Run the experiments many times to obtain a stable measurement.

  • Possibly drop to run level 1 (Unix) so that no other tasks are running.

  • Reboot and rerun the experiments to double-confirm the results.

También considera el tiempo que inviertes para optimizar tu código y si vale la pena la inversión de tiempo que realizas en esto pues hay códigos que casi no son utilizados y otros que sí. No pierdas de vista:

pues es fácil caer en tratar de remover todos los bottlenecks. Sé una persona práctica, define un tiempo objetivo para tu código y optimiza sólo para llegar a ese objetivo.

Unit testing

Además del perfilamiento del código, el unit testing ayuda a validar que cada unidad del software trabaje y se desempeñe como fue diseñada (una unidad puede ser una función, programa, procedimiento, método). El unit testing es importante pues se debe cuidar que el código genere resultados correctos. Puede realizarse independientemente del perfilamiento y si se ha hecho perfilamiento es muy indispensable que se haga un unit testing.

Unos párrafos de la referencia 1. que se utiliza para escribir la nota se mencionan el por qué realizar unit testing después del perfilamiento y una buena práctica para el unit testing:

  • Unit testing during optimization to maintain correctness: ... Ian (blushing) -Ian es uno de los autores del libro- is embarrassed to note that once he spent a day optimizing his code, having disabled unit tests because they were inconvenient, only to discover that his significant speedup result was due to breaking a part of the algorithm he was improving...

  • ...If you try to performance test code deep inside a larger project without separating it from the larger project, you are likely to witness side effects that will sidetrack your efforts. It is likely to be harder to unit test a larger project when you're making fine-grained changes, and this may further hamper your efforts. Side effects could include other threads and processes impacting CPU and memory usage and network and disk activity, which will skew your results.

Referencias

  1. M. Gorelick, I. Ozsvald, High Performance Python, O'Reilly Media, 2014.

  2. https://xkcd.com/