Medir el rendimiento de trozos de código Python según el entorno
Hay un montón de librerías para medir lo rápido que corre un trozo de Python. Se llaman profilers, y la propia librería estándar trae unas cuantas:
timeitprofilecProfile
Funcionan, pero son algo engorrosas de usar, y ninguna está pensada para convivir con el código de producción.
Así que hoy quiero enseñarte una librería minúscula que escribí para cronometrar trozos concretos de Python con el mínimo de ceremonia posible.
Supongamos que tenemos este código:
# fichero: bottleneck_functions.py
import time
def bottleneck_1():
r = 0
for x in range(4000):
r += ((x*x) * x) / 1000
def bottleneck_2():
for x in range(5000):
time.sleep(0.02)
def bottleneck_3():
for x in range(10000):
time.sleep(0.001)
def main():
bottleneck_1()
bottleneck_2()
bottleneck_3()
Después de ejecutarlo seguimos sin saber cuál de las tres funciones es la que frena el programa.
Como decía, podrías tirar de timeit:
import timeit
if __name__ == '__main__':
print(timeit.timeit("bottleneck_1()", setup="from bottleneck_functions import bottleneck_1"))
print(timeit.timeit("bottleneck_2()", setup="from bottleneck_functions import bottleneck_2"))
print(timeit.timeit("bottleneck_3()", setup="from bottleneck_functions import bottleneck_3"))
No es intuitivo, y en una aplicación real se te complica enseguida.
Ahora imagina una aplicación web pequeña que llama a esas tres funciones, y supón que:
-
Queremos medir cuánto tarda cada una.
-
No queremos cambiar el código fuente según el entorno en el que corra la aplicación.
-
Solo queremos que la medición ocurra en desarrollo y en staging.
¿Lo tienes? Bien. Esta es la aplicación web:
# fichero: web_app.py
from flask import Flask
from bottleneck_functions import *
app = Flask(__name__)
@app.route("/", methods=["GET"])
def home():
bottleneck_1()
bottleneck_2()
bottleneck_3()
return "Ok!"
app.run()
Tal cual está, no hay forma de cronometrar cada función y localizar el cuello de botella.
Aquí es donde entra python-performance-tools:
> pip install python-performance-tools
# fichero: web_app_profiling.py
from flask import Flask
from performance_tools import *
from bottleneck_functions import *
app = Flask(__name__)
@app.route("/", methods=["GET"])
def home():
with catch_time("bottleneck function 1"):
bottleneck_1()
with catch_time("bottleneck function 2"):
bottleneck_2()
with catch_time("bottleneck function 3"):
bottleneck_3()
return "Ok!"
app.run()
Arranca la aplicación, abre http://127.0.0.1:5000 y verás algo así:
> python web_app_profiling.py
[.. OMITIDO ... ]
Time: 0.035312797 :: bottleneck function 1
Time: 5.018828881 :: bottleneck function 2
Time: 12.981881883 :: bottleneck function 3
Ya sabemos cuánto tarda cada función.
Pero recuerda que solo queríamos esto en desarrollo y en staging. catch_time acepta un segundo argumento opcional: una función de activación. Vale cualquier función que devuelva un booleano, y el cronómetro solo se ejecuta cuando devuelve True.
# fichero: web_app_profiling.py
import os
from flask import Flask
from performance_tools import *
from bottleneck_functions import *
app = Flask(__name__)
environment = os.environ.get("ENVIRONMENT", None)
def activate_function():
if environment.lower() in ("development", "staging"):
return True
else:
return False
@app.route("/", methods=["GET"])
def home():
with catch_time("bottleneck function 1", activate_function):
bottleneck_1()
with catch_time("bottleneck function 2", activate_function):
bottleneck_2()
with catch_time("bottleneck function 3", activate_function):
bottleneck_3()
return "Ok!"
app.run()
Comprobemos que funciona.
Activar el profiling
> export ENVIRONMENT=development
> python web_app_profiling.py
[.. OMITIDO ... ]
Time: 0.035312797 :: bottleneck function 1
Time: 5.018828881 :: bottleneck function 2
Time: 12.981881883 :: bottleneck function 3
Desactivar el profiling
> export ENVIRONMENT=production
> python web_app_profiling.py
[.. OMITIDO ... ]
[.. SIN INFORMACIÓN DE PROFILING ... ]
Referencias
-
Profilers de la librería estándar de Python: https://docs.python.org/3/library/profile.html
-
timeitde Python: https://docs.python.org/3/library/timeit.html -
Python Performance Tools: https://github.com/cr0hn/python-performance-tools