Medir el tiempo de ejecución parece sencillo, pero pequeños errores de método pueden producir benchmarks engañosos. El módulo time de Python ofrece perf_counter_ns(), un reloj de alto rendimiento que devuelve enteros en nanosegundos. Es útil para comparar operaciones cortas, registrar latencias y evitar pérdidas de precisión por punto flotante en series largas. En esta guía aprenderás a usarlo correctamente, interpretar sus resultados y crear mediciones más confiables.
Qué devuelve perf_counter_ns
time.perf_counter_ns() devuelve el valor actual de un contador de rendimiento en nanosegundos. El número absoluto no representa una fecha. Solo tiene sentido cuando se compara con otra lectura del mismo contador.
from time import perf_counter_ns
inicio = perf_counter_ns()
resultado = sum(i * i for i in range(100_000))
fin = perf_counter_ns()
print(f"Duración: {fin - inicio} ns")
El contador incluye el tiempo transcurrido mientras el proceso espera, por lo que mide latencia total. Python utiliza el reloj de rendimiento de mayor resolución disponible en la plataforma.
Diferencia con perf_counter
perf_counter() devuelve segundos como número de punto flotante. perf_counter_ns() devuelve nanosegundos como entero. Ambas funciones usan el mismo reloj, pero la versión en nanosegundos evita conversiones y redondeos acumulados.
El nombre no garantiza una resolución física de un nanosegundo. La resolución real depende del sistema operativo y del hardware. El sufijo indica la unidad y el tipo entero del resultado.
Convertir a unidades legibles
duracion_ns = fin - inicio
microsegundos = duracion_ns / 1_000
milisegundos = duracion_ns / 1_000_000
segundos = duracion_ns / 1_000_000_000
Conviene conservar el entero original durante los cálculos y convertirlo solo al mostrar o exportar los datos.
Una función reutilizable
from time import perf_counter_ns
from collections.abc import Callable
from typing import TypeVar
T = TypeVar("T")
def medir(funcion: Callable[[], T]) -> tuple[T, int]:
inicio = perf_counter_ns()
resultado = funcion()
duracion = perf_counter_ns() - inicio
return resultado, duracion
resultado, duracion = medir(lambda: sorted(range(50_000), reverse=True))
print(resultado[:3], duracion)
Además de la duración, registra el tamaño de entrada, la versión de Python, la plataforma y el número de repetición.
Medir bloques con un context manager
from contextlib import contextmanager
from time import perf_counter_ns
@contextmanager
def cronometro(etiqueta: str):
inicio = perf_counter_ns()
try:
yield
finally:
duracion = perf_counter_ns() - inicio
print(f"{etiqueta}: {duracion / 1_000_000:.3f} ms")
with cronometro("procesamiento"):
valores = [x ** 2 for x in range(200_000)]
El bloque finally registra la duración incluso si ocurre una excepción.
Por qué una sola medición no basta
El sistema operativo planifica procesos, las cachés se calientan, el recolector de basura puede ejecutarse y otros programas compiten por recursos. Una sola muestra puede capturar ruido. Repite la operación y analiza la distribución.
from statistics import median
from time import perf_counter_ns
muestras = []
for _ in range(30):
inicio = perf_counter_ns()
sum(range(100_000))
muestras.append(perf_counter_ns() - inicio)
print("mediana:", median(muestras), "ns")
print("mínimo:", min(muestras), "ns")
La mediana suele representar mejor la ejecución típica cuando existen picos. El mínimo puede aproximar el coste sin interrupciones, pero no siempre representa producción.
Warm-up y cachés
La primera ejecución puede pagar importaciones, creación de objetos, lecturas de disco, resolución DNS o inicialización de cachés.
def tarea():
return sum(i * i for i in range(20_000))
for _ in range(5):
tarea()
No ocultes el coste de arranque cuando forme parte de la experiencia del usuario.
Cuándo usar timeit
Para microbenchmarks, timeit suele ser más seguro porque repite la operación y ofrece una interfaz específica. perf_counter_ns() es excelente para instrumentar flujos reales, medir tareas asíncronas, registrar latencia y crear métricas personalizadas.
Consulta también medición con timeit, optimización en Python, profiling con cProfile y logging en Python.
Operaciones asíncronas
import asyncio
from time import perf_counter_ns
async def main():
inicio = perf_counter_ns()
await asyncio.sleep(0.05)
duracion = perf_counter_ns() - inicio
print(duracion / 1_000_000, "ms")
asyncio.run(main())
La duración incluye la espera, lo cual es correcto para latencia de extremo a extremo. Para medir CPU, considera process_time_ns().
Elegir el reloj correcto
time_ns() representa tiempo de calendario y puede ajustarse. monotonic_ns() no retrocede y sirve para intervalos. perf_counter_ns() también es monotónico y usa un contador de alto rendimiento. process_time_ns() mide CPU consumida por el proceso y excluye espera o suspensión.
Errores frecuentes
No compares valores absolutos de máquinas diferentes ni después de reinicios. No interpretes el número como timestamp. Evita incluir impresión, logging, creación de datos o configuración no relacionada dentro del bloque medido.
Tampoco saques conclusiones de diferencias mínimas con pocas muestras. Utiliza entradas realistas, repite las pruebas y registra la variación. El rendimiento depende de datos, cachés, almacenamiento, red, concurrencia, compilación de Python y hardware.
Percentiles de latencia
Los promedios de producción pueden ocultar colas lentas. Guarda muchas duraciones y calcula percentiles como p50, p95 y p99. Una API puede tener una media baja y aun así perjudicar a parte de los usuarios. Conserva valores en nanosegundos o conviértelos a milisegundos para tu sistema de métricas.
Buenas prácticas
Define claramente el inicio y el final de la operación. Haz calentamiento solo cuando corresponda. Usa múltiples repeticiones y datos representativos. Separa tiempo de CPU de latencia total. Compara en el mismo entorno y registra versiones. Antes de optimizar, perfila para localizar un cuello de botella real.
La referencia principal es la documentación oficial de time. Para microbenchmarks controlados, consulta la documentación de timeit.
Conclusión
perf_counter_ns() es una herramienta sencilla y potente para medir duraciones con enteros y alta resolución. Funciona bien para instrumentación, latencia, context managers y pruebas personalizadas. Sin embargo, la confiabilidad depende más del método que de la precisión del reloj. Repite, controla el entorno, usa cargas realistas, elige estadísticas adecuadas y documenta qué incluye cada intervalo. Así, los nanosegundos se convierten en decisiones de optimización mucho más seguras.







