Medir mal es peor que no medir, porque te deja seguro de algo falso. Python trae dos herramientas que lo hacen bien y nadie tiene excusa para no usarlas.
También te puede interesar
La medición ingenua
t0 = time.perf_counter()
con_mas(piezas)
print(f"{(time.perf_counter()-t0)*1e6:.1f} µs")
salidavuelta 1: 19.1 µs vuelta 2: 16.1 µs vuelta 3: 14.8 µs
El mismo código, tres veces, tres cifras distintas, con un 29 % de diferencia entre la primera y la última. Una sola medida no dice nada.
timeit: repite y se queda con lo mejor
import timeit t = timeit.Timer(lambda: con_mas(piezas)) n, _ = t.autorange() # cuántas repeticiones hacen falta mejor = min(t.repeat(repeat=5, number=n)) / n
salida+= 8.97 µs (20000 repeticiones por tanda) join 1.75 µs (200000 repeticiones por tanda) lista+join 4.29 µs (50000 repeticiones por tanda)
Tres cosas que hace timeit y tu cronómetro no: ejecuta muchas veces para que el ruido se diluya, repite la tanda varias veces, y se queda con el mínimo, no con la media. El mínimo es lo correcto aquí: las medidas altas son interferencias de otros procesos, no información sobre tu código.
Y desde la terminal, sin escribir un programa:
python3 -m timeit -s 'x=list(range(1000))' 'sum(x)'
La medida que cambia cómo escribes
lista, conj = list(range(100_000)), set(range(100_000)) 99_999 in lista 99_999 in conj
salida99999 in list 759418.2 ns 99999 in set 33.5 ns
if x in lista dentro de un bucle, eso es cuadrático y es, con diferencia, el fallo de rendimiento más común en Python. Convertir la lista a set antes del bucle suele ser todo el arreglo.Dónde se va el tiempo
import cProfile, pstats
cProfile.run("trabajo()", sort="cumulative")
# o, para controlar la salida:
pr = cProfile.Profile(); pr.enable(); trabajo(); pr.disable()
pstats.Stats(pr).sort_stats("cumulative").print_stats(5)
salidancalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.001 0.001 Rend.py:38(trabajo)
50 0.001 0.000 0.001 0.000 Rend.py:5(con_mas)
50 0.000 0.000 0.000 0.000 Rend.py:9(con_join)
50 0.000 0.000 0.000 0.000 {method 'join' of 'str' objects}
| Columna | Qué significa |
|---|---|
ncalls |
cuántas veces se llamó |
tottime |
tiempo dentro de la función, sin contar lo que llama |
cumtime |
tiempo total, incluyendo lo que llama |
Se ordena por cumulative para encontrar la rama cara, y por tottime para encontrar la función cara. Y la distinción clave: timeit dice cuánto tarda algo; cProfile dice qué parte de tu programa lo está tardando. Son preguntas distintas.
cProfile añade bastante sobrecarga a cada llamada, así que los tiempos absolutos que da no son fiables; las proporciones sí. Si necesitas medir con poca interferencia, hay perfiladores por muestreo como py-spy, que además se enganchan a un proceso que ya está corriendo.El orden de las cosas
- Mide primero. La función que crees que es el cuello de botella casi nunca lo es.
- Perfila para saber dónde, con
cProfile. - Mide esa parte con
timeit, antes y después del cambio. - Y antes de optimizar nada, mira si la estructura de datos es la correcta. El salto de lista a conjunto de ahí arriba vale más que cualquier microoptimización.
Todo el código se ejecutó con Python 3.14 en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.