18.2. Uso del módulo timeit

La cosa más importante que debe saber sobre optimización de código en Python es que no debería escribir su propia función de cronometraje.

Cronometrar partes pequeñas de código es algo increíblemente complicado. ¿Cuánto tiempo dedica el computador a ejecutar este código? ¿Hay cosas ejecutándose en segundo plano? ¿Está seguro? Cada computador moderno tiene procesos en segundo plano, algunos todo el tiempo y otros intermitentemente. Se lanzan tareas de cron a intervalos regulares; se “despiertan” ocasionalmente servicios en segundo plano para hacer cosas útiles como buscar correo nuevo, conectar a servidores de mensajería instantánea, buscar actualizaciones de aplicaciones, buscar virus, comprobar si se ha insertado un disco en la unidad de CD en los últimos 100 nanosegundos, etc.. Antes de empezar a cronometrar, apague ese servicio que comprueba incesantemente si ha vuelto la conexión a red, y entonces...

Y entonces está el asunto de las variaciones que introduce el propio marco cronometrado. ¿Consulta el intérprete de Python los nombres de los métodos en una caché? ¿Hace caché de bloques de código compilado? ¿De expresiones regulares? ¿Tendrá su código efectos secundarios si se ejecuta más de una vez? No olvide que estamos tratando con pequeñas fracciones de un segundo, así que incluso errores minúsculos en el cronometrado fastidiarán irreparablemente los resultados.

La comunidad de Python tiene un dicho: “Python incluye las baterías.” No escriba su propia infraestructura para cronometrar. Python 2.3 incluye una perfectamente buena llamada timeit.

Ejemplo 18.2. Presentación de timeit

Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.

>>> import timeit
>>> t = timeit.Timer("soundex.soundex('Pilgrim')",
...     "import soundex")   1
>>> t.timeit()              2
8.21683733547
>>> t.repeat(3, 2000000)    3
[16.48319309109, 16.46128984923, 16.44203948912]
1 El módulo timeit define una clase Timer que toma dos argumentos. Ambos argumentos son cadenas. El primero es una sentencia que deseamos cronometrar; en este caso cronometramos una llamada a la función Soundex dentro de soundex con 'Pilgrim' como argumento. El segundo argumento de la clase Timer es la sentencia que importará el entorno de la ejecución. timeit crea internamente un entorno virtual aislado, ejecuta manualmente la sentencia de configuración (importa el módulo soundex), y luego compila y ejecuta manualmente la sentencia cronometrada (invocando la función Soundex).
2 Una vez tenemos el objeto Timer, lo más sencillo es invocar timeit(), que llama a nuestra función 1 millón de veces y devuelve el número de segundos que le llevó hacerlo.
3 El otro método principal del objeto Timer es repeat(), que toma dos argumentos opcionales. El primero es el número de veces que habrá de repetir la prueba completa, y el segundo es el número de veces que ha de llamar a la sentencia cronometrada dentro de cada prueba. Ambos argumentos son opcionales y por omisión serán 3 y 1000000 respectivamente. El método repeat() devuelve una lista del tiempo en segundos que llevó terminar cada ciclo de prueba.
sugerencia
Podemos usar el módulo timeit desde la línea de órdenes para probar un programa de Python que ya exista, sin modificar el código. Vea http://docs.python.org/lib/node396.html si desea documentación sobre las opciones para línea de órdenes.

Observe que repeat() devuelve una lista de tiempos. Los tiempos serán diferentes casi siempre, debido a ligeras variaciones en la cantidad de tiempo de procesador que se le asigna al intérprete de Python (y esos dichosos procesos en segundo plano de los que no se pudo librar). Su primera idea podría ser decir “Hallemos la media, a la que llamaremos El Número Verdadero.

De hecho, eso es incorrecto casi con seguridad. Las pruebas que tardaron más no lo hicieron debido a variaciones en el código o en el intérprete de Python; sino debido a esos fastidiosos procesos en segundo plano, y otros factores ajenos al intérprete de Python que no se pudieron eliminar completamente. Si los diferentes resultados de cronometraje difieren por más de un pequeño porcentaje, aún tenemos demasiada variabilidad para confiar en los resultados. En caso contrario tome el valor más pequeño y descarte los demás.

Python tiene una función min bastante a mano que toma una lista y devuelve el valor más pequeño:

>>> min(t.repeat(3, 1000000))
8.22203948912
sugerencia
El módulo timeit sólo sirve de algo si ya sabe qué parte de su código quiere optimizar. Si tiene un programa grande escrito en Python y no sabe dónde tiene los problemas de rendimiento, pruebe el módulo hotshot.