Medir o tempo de execução parece simples, mas pequenos detalhes podem tornar um benchmark enganoso. O módulo time oferece a função perf_counter_ns(), um relógio de alta resolução que retorna inteiros em nanossegundos. Ela é especialmente útil para comparar trechos curtos, registrar latências e evitar a perda de precisão que pode aparecer quando valores de ponto flutuante são acumulados. Neste guia, você aprenderá como usar essa função corretamente, interpretar seus resultados e construir medições mais confiáveis.
O que é perf_counter_ns
time.perf_counter_ns() devolve o valor atual de um contador de desempenho em nanossegundos. O número absoluto não representa uma data ou horário do calendário. Ele só faz sentido quando comparado com outra leitura do mesmo contador. Para medir uma duração, registre um valor antes da operação, outro depois e calcule a diferença.
from time import perf_counter_ns
inicio = perf_counter_ns()
resultado = sum(i * i for i in range(100_000))
fim = perf_counter_ns()
print(f"Duração: {fim - inicio} ns")
O contador inclui o tempo decorrido enquanto o processo está bloqueado ou aguardando o sistema, por isso é adequado para medir latência percebida. Ele também usa a maior resolução disponível no sistema operacional.
Diferença para perf_counter
perf_counter() retorna segundos como número de ponto flutuante. Já perf_counter_ns() retorna nanossegundos como inteiro. As duas funções usam o mesmo relógio de desempenho, mas a versão em nanossegundos evita conversões e problemas de arredondamento em medições pequenas ou em séries muito longas.
from time import perf_counter, perf_counter_ns
inicio_s = perf_counter()
inicio_ns = perf_counter_ns()
# operação medida
fim_s = perf_counter()
fim_ns = perf_counter_ns()
Isso não significa que o sistema realmente meça cada nanossegundo. A resolução efetiva depende da plataforma. O sufixo _ns indica apenas a unidade e o tipo inteiro do valor retornado.
Convertendo para unidades legíveis
Embora nanossegundos sejam úteis para cálculos, normalmente você apresentará resultados em microssegundos, milissegundos ou segundos.
duracao_ns = fim - inicio
microssegundos = duracao_ns / 1_000
milissegundos = duracao_ns / 1_000_000
segundos = duracao_ns / 1_000_000_000
Mantenha o valor original inteiro durante os cálculos e faça a conversão apenas no momento de exibir ou exportar. Essa prática reduz arredondamentos acumulados.
Criando um cronômetro reutilizável
Uma função auxiliar torna as medições mais consistentes.
from time import perf_counter_ns
from collections.abc import Callable
from typing import TypeVar
T = TypeVar("T")
def medir(funcao: Callable[[], T]) -> tuple[T, int]:
inicio = perf_counter_ns()
resultado = funcao()
duracao = perf_counter_ns() - inicio
return resultado, duracao
resultado, duracao = medir(lambda: sorted(range(50_000), reverse=True))
print(resultado[:3], duracao)
O retorno preserva tanto o resultado da função quanto a duração. Em código real, registre também informações como tamanho da entrada, versão do Python, plataforma e número de repetição.
Usando um context manager
Para medir blocos maiores, um gerenciador de contexto deixa o código mais limpo.
from contextlib import contextmanager
from time import perf_counter_ns
@contextmanager
def cronometro(rotulo: str):
inicio = perf_counter_ns()
try:
yield
finally:
duracao = perf_counter_ns() - inicio
print(f"{rotulo}: {duracao / 1_000_000:.3f} ms")
with cronometro("processamento"):
dados = [x ** 2 for x in range(200_000)]
O bloco finally garante que a duração seja registrada mesmo quando uma exceção ocorre.
Por que uma única medição é insuficiente
O sistema operacional agenda processos, o interpretador aquece caches, o coletor de lixo pode executar e outros programas disputam CPU e memória. Uma medição isolada pode capturar um ruído incomum. Repita várias vezes e analise a distribuição.
from statistics import median
from time import perf_counter_ns
amostras = []
for _ in range(30):
inicio = perf_counter_ns()
sum(range(100_000))
amostras.append(perf_counter_ns() - inicio)
print("mediana:", median(amostras), "ns")
print("mínimo:", min(amostras), "ns")
A mediana costuma representar melhor a execução típica do que a média quando existem picos. O menor valor pode aproximar o custo sem interrupções externas, mas não representa necessariamente a experiência real em produção.
Warm-up e caches
A primeira execução pode pagar custos de importação, criação de estruturas, leitura de disco, resolução de DNS ou preenchimento de caches. Separe aquecimento de medição quando isso fizer sentido.
def tarefa():
return sum(i * i for i in range(20_000))
for _ in range(5):
tarefa()
# medições reais começam depois do aquecimento
Não esconda o warm-up quando ele fizer parte da experiência do usuário, como na inicialização de uma aplicação. A decisão depende do que você deseja medir.
perf_counter_ns não substitui timeit
Para microbenchmarks, o módulo timeit é geralmente mais seguro porque repete a operação, reduz interferências comuns e oferece uma interface específica. perf_counter_ns() é melhor quando você precisa instrumentar fluxos reais, medir blocos assíncronos, registrar latências ou criar métricas personalizadas.
Você pode revisar como medir código com timeit, dicas de otimização em Python, profiling com cProfile e logging em Python.
Medindo operações assíncronas
O contador funciona normalmente com asyncio.
import asyncio
from time import perf_counter_ns
async def main():
inicio = perf_counter_ns()
await asyncio.sleep(0.05)
duracao = perf_counter_ns() - inicio
print(duracao / 1_000_000, "ms")
asyncio.run(main())
Nesse caso, a duração inclui o período de espera. Isso é correto quando o objetivo é medir latência de ponta a ponta. Para medir apenas CPU, use ferramentas e relógios apropriados, como process_time_ns().
Comparação com outros relógios
time_ns() representa tempo de calendário e pode sofrer ajustes do sistema. monotonic_ns() não volta para trás e é adequado para intervalos. perf_counter_ns() também é monotônico e seleciona o contador de maior resolução para desempenho. process_time_ns() mede tempo de CPU consumido pelo processo e exclui períodos de espera. Escolha o relógio de acordo com a pergunta que deseja responder.
Erros comuns
Não compare valores coletados em máquinas diferentes ou após reinicializações. Não trate o número absoluto como timestamp. Evite incluir print, logging ou preparação de dados dentro do bloco quando esses custos não fazem parte do teste. Também não conclua que uma versão é melhor com base em diferenças mínimas e poucas amostras.
Outro erro é testar entradas artificiais que não representam produção. Desempenho depende do tamanho, distribuição dos dados, cache, disco, rede e concorrência. Documente o ambiente e repita o teste sob condições comparáveis.
Registrando percentis de latência
Em aplicações, a média esconde picos. Registre várias durações e calcule percentis como p50, p95 e p99. Uma API pode ter média baixa, mas ainda apresentar caudas lentas que prejudicam usuários. Armazene as durações em nanossegundos ou converta para milissegundos no sistema de métricas.
Boas práticas
Defina claramente o início e o fim da operação, aqueça quando apropriado, faça várias repetições, use entradas realistas, separe tempo de CPU de latência total e evite otimizações antes de encontrar um gargalo real. Compare resultados no mesmo ambiente e versionamento.
A referência principal é a documentação oficial do módulo time. Para princípios de benchmarking, consulte também a documentação do módulo timeit.
Conclusão
perf_counter_ns() é uma ferramenta simples e poderosa para medir durações com valores inteiros e alta resolução. Ela funciona bem em instrumentação, testes de latência, context managers e medições personalizadas. Resultados confiáveis, porém, dependem mais do método do que do relógio: repita, aqueça quando necessário, controle o ambiente, escolha métricas adequadas e interprete variações com cuidado. Com essas práticas, você transforma números de nanossegundos em decisões de otimização muito mais seguras.







