Otimização de Desempenho, Cython e Numba
Perfile gargalos Python, use __slots__, collections e NumPy, acelere com Cython e compilação JIT Numba
Otimização de Desempenho, Cython e Numba
Perfile Antes de Otimizar
Nunca adivinhe onde está o gargalo. Use profilers.
cProfile
import cProfile
import pstats
def heavy():
total = 0
for i in range(10**6):
total += i ** 2
return total
profiler = cProfile.Profile()
profiler.runcall(heavy)
stats = pstats.Stats(profiler)
stats.sort_stats(pstats.SortKey.TIME)
stats.print_stats(10)A saída mostra tempo cumulativo, tempo por chamada e contagem de chamadas.
Line Profiler
pip install line_profilerfrom line_profiler import profile
@profile
def process_data(data):
total = 0.0
for x in data:
total += x ** 2
total /= max(1, x)
return total
process_data(range(100_000))Execute com kernprof -l script.py && python -m line_profiler script.py.lprof para temporização linha por linha.
Memory Profiler
from memory_profiler import profile
@profile
def allocate():
big = [list(range(1000)) for _ in range(1000)]
return sum(len(x) for x in big)
allocate()Usando __slots__
Slots reduzem memória substituindo o __dict__ por instância por um array de tamanho fixo.
class WithoutSlots:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
class WithSlots:
__slots__ = ("x", "y", "z")
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
import sys
a = WithoutSlots(1, 2, 3)
b = WithSlots(1, 2, 3)
print(sys.getsizeof(a)) # ~56 (mais __dict__ ~120)
print(sys.getsizeof(b)) # ~56 (sem __dict__)
# Para 1M instâncias, WithSlots economiza ~120+ MBUse __slots__ ao criar milhões de objetos pequenos (ex.: registros de dados, entidades de jogo, partículas).
Otimizações do Módulo collections
from collections import defaultdict, Counter, deque, OrderedDict
from collections.abc import Mapping
# deque para appends/pops O(1) em ambas as extremidades
dq = deque(maxlen=1000)
for i in range(2000):
dq.append(i)
print(len(dq)) # 1000 (mais antigo descartado)
# Counter para frequência
freq = Counter("mississippi")
print(freq.most_common(2)) # [('i', 4), ('s', 4)]
# defaultdict evita verificações de chave
groups = defaultdict(list)
groups["a"].append(1) # sem KeyErrorVetorização com NumPy
Loops Python nativos são lentos; NumPy opera em arrays em nível C.
import numpy as np
import time
# Loop Python lento
N = 10_000_000
start = time.perf_counter()
py_result = sum(x ** 2 for x in range(N))
print(f"Python: {time.perf_counter() - start:.2f}s")
# NumPy rápido
start = time.perf_counter()
arr = np.arange(N, dtype=np.float64)
np_result = (arr ** 2).sum()
print(f"NumPy: {time.perf_counter() - start:.2f}s")
# Tipicamente 50-100x mais rápidoConceitos Básicos de Cython
Cython compila código semelhante ao Python para extensões C. Salve como .pyx.
# sum_squares.pyx
def sum_squares(int n):
cdef int i
cdef long long total = 0
for i in range(n):
total += i * i
return totalCompilar com setup.py
from setuptools import setup, Extension
from Cython.Build import cythonize
setup(
ext_modules=cythonize([
Extension("sum_squares", ["sum_squares.pyx"])
])
)python setup.py build_ext --inplace
python -c "import sum_squares; print(sum_squares.sum_squares(10**7))"Cython permite declarações de tipo cdef que compilam para C puro. Mesmo sem anotações de tipo, Cython frequentemente dá aceleração de 2-3x.
Modo Python Puro com Anotações Cython
import cython
@cython.cfunc
@cython.returns(cython.longlong)
@cython.locals(n=cython.int, i=cython.int)
def sum_squares(n):
total: cython.longlong = 0
for i in range(n):
total += i * i
return totalCompilação JIT com Numba
Numba compila funções Python para código de máquina usando LLVM — zero código C necessário.
from numba import njit, prange
import time
import math
@njit
def is_prime(n):
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
@njit(parallel=True)
def count_primes(limit):
count = 0
for i in prange(2, limit):
if is_prime(i):
count += 1
return count
start = time.perf_counter()
print(count_primes(10_000_000)) # 664,579
print(f"Numba: {time.perf_counter() - start:.2f}s")
# Frequentemente 100-200x mais rápido que Python puroNumba é excelente com loops numéricos e código com muita matemática. É amplamente usado em finanças quantitativas, computação científica e pré-processamento de ML.
Matriz de Decisão: Cython vs Numba
| Característica | Cython | Numba |
|---|---|---|
| Configuração | Requer etapa de compilação | JIT em tempo de execução |
| Dependências | Compilador C, Cython | llvmlite, numpy |
| Melhor para | Interoperabilidade C complexa | Algoritmos numéricos |
| Recursos Python | Limitados (sem tipagem dinâmica) | Mais compatível |
| Deployment | Build wheel | Fácil (sem build) |
| Velocidade | Próximo de C | Próximo de C |
Mundo Real: Processamento de Imagem com Numba
import numpy as np
from numba import njit, prange
@njit(parallel=True)
def grayscale(images):
"""Converter lote de imagens RGB para escala de cinza."""
n, h, w, c = images.shape
result = np.zeros((n, h, w), dtype=np.uint8)
for i in prange(n):
for y in range(h):
for x in range(w):
r, g, b = images[i, y, x]
result[i, y, x] = 0.299 * r + 0.587 * g + 0.114 * b
return result
batch = np.random.randint(0, 256, (100, 256, 256, 3), dtype=np.uint8)
gray = grayscale(batch)Perguntas de Prática
- Qual é a diferença entre
cProfilee um line profiler? Quando você usaria cada um? - Escreva um benchmark comparando uma compreensão de lista vs um loop
forvs NumPy para computarx**2em 10M elementos. - Como
__slots__reduz o uso de memória? Quais são as desvantagens? - Crie uma função JIT Numba que computa o conjunto de Mandelbrot e compare sua velocidade com Python puro.
- O que é
cython -ae como ajuda a otimizar código? - Compare
dequevslistpara uma operação de janela deslizante em 100K elementos. - Escreva um arquivo
.pyxCython que computa números de Fibonacci eficientemente usandocdef. - Por que o loop
fordo Python é mais lento que NumPy para operações numéricas? Explique o papel do bytecode CPython. - Implemente uma análise de frequência baseada em
Counterem uma lista de 10M itens e comparecollections.Countervs dict manual. - Quais são as limitações do Numba? Quando Cython seria a melhor escolha apesar da etapa extra de build?