Noções Básicas de Desempenho
11 exemplos para você começar com Desempenho - 8 básicos e 3 intermediários.
Pré-requisitos
- Python 3.14.0
- Módulos da biblioteca padrão:
timeit,cProfile,tracemalloc - Opcional:
uv add numpypara exemplos de vetorização
Exemplos Básicos
1. Meça Antes de Otimizar
Nunca otimize sem uma medição de linha de base.
import time
def slow_sum(n: int) -> int:
return sum(range(n))
start = time.perf_counter()
result = slow_sum(1_000_000)
elapsed = time.perf_counter() - start
print(f"Resultado: {result}, Tempo: {elapsed:.4f}s")time.perf_counter()é monotônico e de alta resolução- Registre a linha de base antes de qualquer tentativa de otimização
- Compare após as alterações para verificar a melhoria
- Se a medição for ruidosa, execute várias iterações
Relacionado: timeit & Microbenchmarks - temporização precisa
2. timeit para Microbenchmarks
O módulo timeit executa código em loops apertados para uma temporização estável.
import timeit
elapsed = timeit.timeit("sum(range(1000))", number=10000)
print(f"Média: {elapsed / 10000 * 1e6:.2f} μs")numbercontrola as iterações do loop- Isola a instrução da sobrecarga de configuração
- Use para comparar duas abordagens em pequenos caminhos de código
- Não é adequado para operações vinculadas a I/O
Relacionado: timeit & Microbenchmarks - metodologia
3. cProfile para Hotspots de CPU
Encontre quais funções consomem mais tempo de CPU.
import cProfile
import pstats
def work():
sum(i * i for i in range(100_000))
cProfile.run("work()", "profile.out")
stats = pstats.Stats("profile.out")
stats.sort_stats("cumulative").print_stats(10)cumulativeordena pelo tempo total, incluindo subchamadas- As principais entradas são candidatos à otimização
- Perfis de cargas de trabalho reais, não exemplos triviais
- Salve as estatísticas em um arquivo para comparação entre execuções
Relacionado: cProfile & Profilers - flame graphs
4. tracemalloc para Memória
Rastreie alocações de memória para encontrar vazamentos e inchaços.
import tracemalloc
tracemalloc.start()
data = [bytearray(1024) for _ in range(10_000)]
snapshot = tracemalloc.take_snapshot()
top = snapshot.statistics("lineno")[:5]
for stat in top:
print(stat)
tracemalloc.stop()tracemallocé da biblioteca padrão - nenhuma instalação é necessária- Mostra qual linha alocou mais memória
- Compare snapshots antes/depois para encontrar vazamentos
- Mais leve que o memray para verificações rápidas
Relacionado: Memory Profiling - mergulho profundo no memray
5. List Comprehension vs Loop
Compreensões são mais rápidas para transformações simples.
# Mais rápido
squares = [x * x for x in range(100_000)]
# Mais lento
squares = []
for x in range(100_000):
squares.append(x * x)- Compreensões evitam pesquisas repetidas de
list.append - Para lógica complexa, a legibilidade pode superar o ganho de velocidade
- Perfis antes de assumir que a compreensão vence
- Expressões geradoras economizam memória para sequências grandes
6. functools.lru_cache
Armazene em cache os resultados de funções puras para evitar computação redundante.
from functools import lru_cache
@lru_cache(maxsize=256)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # instantâneo com cache- Funciona apenas para funções com argumentos "hashable"
maxsizelimita o uso de memória- Thread-safe no CPython
- Use
.cache_info()para inspecionar a taxa de acertos
Relacionado: Caching & Memoization - estratégias de cache
7. Gerador vs Lista
Geradores produzem itens sob demanda, economizando memória em grandes conjuntos de dados.
def read_lines(path: str):
with open(path) as f:
for line in f:
yield line.strip()
# Processa uma linha por vez - memória constante
for line in read_lines("huge.log"):
if "ERROR" in line:
print(line)- Listas carregam tudo na memória
- Geradores processam um item por vez
- Use geradores para I/O de arquivos e estágios de pipeline
sum(1 for _ in gen)ainda consome o gerador uma vez
8. Evite Pesquisa Repetida de Atributos
Vincule atributos acessados com frequência a variáveis locais em loops "quentes".
# Mais rápido em loops apertados
append = results.append
for item in data:
append(transform(item))
# Mais lento - pesquisa repetida de atributos
for item in data:
results.append(transform(item))- O acesso a variáveis locais é mais rápido que a pesquisa de atributos
- Importa apenas nos loops internos "quentes"
- Compromisso de legibilidade - aplique apenas onde foi perfilado
- O Python moderno otimiza muitos casos automaticamente
Exemplos Intermediários
9. Vetorização NumPy
Substitua loops Python por operações de array para trabalho numérico.
import numpy as np
data = np.arange(1_000_000, dtype=np.float64)
result = np.sqrt(data) * 2.0 + 1.0 # vetorizado
# vs: [math.sqrt(x) * 2.0 + 1.0 for x in data] # 10-100x mais lento- Operações NumPy são executadas em C/Fortran nos bastidores
- Requer dados numéricos contíguos
- pandas e Polars são construídos sobre vetorização semelhante
- A sobrecarga de conversão importa para arrays pequenos
Relacionado: Vectorization with NumPy - guia completo
10. asyncio para Trabalho Vinculado a I/O
Paralelize a espera em rede/disco sem threads.
import asyncio
import httpx
async def fetch_all(urls: list[str]) -> list[int]:
async with httpx.AsyncClient() as client:
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
return [r.status_code for r in responses]
asyncio.run(fetch_all(["https://example.com"] * 10))- asyncio ajuda quando o código espera por I/O, não por CPU
- Não contorna o GIL para trabalho vinculado à CPU
- Use
asyncio.gatherpara corrotinas concorrentes - Para paralelismo de CPU, use
multiprocessingouconcurrent.futures
Relacionado: Async & Concurrency for Throughput - padrões
11. Fluxo de Trabalho de Auditoria de Desempenho
Uma passagem sistemática antes de afirmar que algo é "otimizado".
# 1. Meça a linha de base
# 2. Perfis (cProfile ou py-spy)
# 3. Corrija apenas o hotspot principal
# 4. Re-meça
# 5. Documente os números antes e depois
baseline_ms = 450
optimized_ms = 120
improvement = (baseline_ms - optimized_ms) / baseline_ms * 100
print(f"Melhoria: {improvement:.0f}%") # 73%- Otimize o gargalo medido, não um palpite
- Uma alteração por vez para atribuição clara
- Mantenha um registro das decisões de otimização
- Pare quando o desempenho atender aos requisitos
Relacionado: Performance Audit Checklist - checklist completo
Versões da Stack: Esta página foi escrita para Python 3.14.0, FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ e uv 0.6+.