- Resultado de benchmark que mede de forma sistemática as métricas de desempenho de operações, memória e entrada/saída no Python, quantificando o tempo e o uso de memória de cada operação
- Em velocidade, apresenta a latência relativa de várias operações, como acesso a atributo em 14ns, adição a lista em 29ns, abertura de arquivo em 9μs e resposta do FastAPI em 8.6μs
- Em memória, traz números concretos como string vazia com 41 bytes, inteiro com 28 bytes, lista vazia com 56 bytes, dicionário vazio com 64 bytes e processo vazio com 16MB
- Compara as diferenças de desempenho entre a biblioteca padrão e bibliotecas alternativas (
orjson, msgspec etc.) em áreas como estruturas de dados, serialização e processamento assíncrono
- Como lições principais, destaca o alto overhead de memória dos objetos Python, a rapidez de consulta em dict/set, o efeito de economia de memória de
__slots__ e a necessidade de considerar o overhead do processamento assíncrono
Visão geral
- Material que organiza indicadores de desempenho que desenvolvedores Python devem conhecer, apresentando valores reais medidos para velocidade de operações e uso de memória
- Os benchmarks foram executados em CPython 3.14.2, em um Mac Mini M4 Pro (ARM, 14 núcleos, 24GB RAM)
- Os resultados priorizam a comparação relativa, e o código e os dados estão disponíveis em um repositório no GitHub
Uso de memória (Memory Costs)
- Um processo Python vazio usa 15.73MB de memória
- Strings têm base de 41 bytes, com 1 byte adicional por caractere
- Ex.: string vazia 41B, string de 100 caracteres 141B
- Tipos numéricos: inteiros pequenos (0–256) 28B, inteiros maiores (1000) também 28B, inteiro muito grande (10ⁱ⁰⁰) 72B, ponto flutuante 24B
- Tamanho base de coleções: lista 56B, dicionário 64B, conjunto 216B
- Com 1.000 itens: lista 35.2KB, dicionário 63.4KB, conjunto 59.6KB
- Instâncias de classe: classe comum (5 atributos) 694B, classe com
__slots__ 212B
- Com 1.000 instâncias: classe comum 165.2KB, classe com
__slots__ 79.1KB
Operações básicas (Basic Operations)
- Operações aritméticas: soma de inteiros 19ns, soma de float 18.4ns, multiplicação de inteiros 19.4ns
- Operações com strings: concatenação 39.1ns, f-string 64.9ns,
.format() 103ns, formatação com % 89.8ns
- Operações com listas:
append() 28.7ns, list comprehension (1.000 itens) 9.45μs, mesmo loop for 11.9μs
- List comprehension é cerca de 26% mais rápida que loop
for
Acesso e iteração em coleções (Collection Access and Iteration)
- Acesso por chave/índice: consulta em dicionário 21.9ns, pertencimento em conjunto 19ns, acesso por índice em lista 17.6ns
- Teste de pertencimento em lista (1.000 itens) leva 3.85μs, cerca de 200 vezes mais lento que set/dict
- Verificação de tamanho:
len() leva 18.8ns para lista, 17.6ns para dicionário e 18ns para conjunto
- Iteração: lista (1.000 itens) 7.87μs, dicionário 8.74μs,
sum() 1.87μs
Classes e atributos (Class and Object Attributes)
- Velocidade de acesso a atributos: tanto classe comum quanto classe com
__slots__ leem em 14.1ns e escrevem em cerca de 16ns
- Outras operações: leitura com
@property 19ns, getattr() 13.8ns, hasattr() 23.8ns
- Ao usar
__slots__, o efeito de economia de memória é mais de 2x, com velocidade de acesso no mesmo nível
JSON e serialização (JSON and Serialization)
- Desempenho de bibliotecas alternativas em relação à biblioteca padrão
orjson serializa objetos complexos em 310ns, mais de 8 vezes mais rápido que os 2.65μs de json
msgspec fica em 445ns, e ujson em 1.64μs
- Na desserialização,
orjson também é o mais rápido, com 839ns
- Pydantic:
model_dump_json() 1.54μs, model_validate_json() 2.99μs
Frameworks web (Web Frameworks)
- Para a mesma resposta JSON, FastAPI 8.63μs, Starlette 8.01μs, Litestar 8.19μs, Flask 16.5μs, Django 18.1μs
- O FastAPI responde cerca de 2 vezes mais rápido que o Django
Entrada/saída de arquivos (File I/O)
- Abrir e fechar arquivo: 9.05μs, ler 1KB: 10μs, ler 1MB: 33.6μs
- Escrita: 1KB 35.1μs, 1MB 207μs
- Pickle é cerca de 2 vezes mais rápido que
json tanto na serialização quanto na desserialização (pickle.dumps() 1.3μs, json.dumps() 2.72μs)
Banco de dados e cache (Database and Persistence)
- SQLite: insert 192μs, select 3.57μs, update 5.22μs
- diskcache: set 23.9μs, get 4.25μs
- MongoDB: insert 119μs, find_one 121μs
- O SQLite é o mais rápido em leitura, enquanto o diskcache se destaca em desempenho de escrita
Chamadas de função e exceções (Function and Call Overhead)
- Chamadas de função: função vazia 22.4ns, método 23.3ns, lambda 19.7ns
- Tratamento de exceções: try/except (sem exceção) 21.5ns, com exceção lançada 139ns
- Verificação de tipo:
isinstance() 18.3ns, comparação com type() 21.8ns
Overhead assíncrono (Async Overhead)
- Criação de coroutine 47ns,
run_until_complete 27.6μs
asyncio.sleep(0) 39.4μs, gather(10 coroutines) 55μs
- Em comparação com chamada de função síncrona (20ns), a execução assíncrona (28μs) é cerca de 1.000 vezes mais lenta
Principais lições (Key Takeaways)
- O overhead de memória dos objetos Python é alto; até uma lista vazia usa 56 bytes
- Consultas em dicionários e conjuntos são centenas de vezes mais rápidas que busca em lista
- Bibliotecas JSON alternativas como
orjson e msgspec são de 3 a 8 vezes mais rápidas que a padrão
- O processamento assíncrono tem overhead alto, então é recomendado apenas quando paralelismo for necessário
__slots__ reduz a memória para menos da metade, com quase nenhuma perda de desempenho
1 comentários
Comentários do Hacker News
Muita gente diz que “se você precisa se preocupar com números de latência em Python, deveria usar outra linguagem”, mas eu não concordo.
Grandes codebases como Instagram, Dropbox e OpenAI também cresceram com Python. No fim, você encontra problemas de desempenho, e é importante ter a capacidade de resolvê-los dentro do próprio Python sem migrar para outra linguagem.
A maioria dos problemas de performance não vem dos limites da linguagem, mas de código ineficiente. Por exemplo, um loop que repete chamadas de função 10 mil vezes sem necessidade.
Vale a pena conferir também o quiz de latência em Python que eu fiz
Paradoxalmente, no momento em que esses números passam a importar, Python já não é a ferramenta certa para esse trabalho
Na prática, o importante é instrumentar o código e encontrar os gargalos (com ferramentas como pyspy). Se você está preocupado com a velocidade de adicionar elementos a uma lista, essa operação não deveria estar sendo feita em Python
Essa abordagem é possível graças à interoperabilidade entre Python e C. Zig também está ficando cada vez melhor. Eu não pilotaria um avião com Python, mas noção de recursos continua sendo importante
Saber quantos bytes uma string vazia ocupa não tem muito valor. O importante é entender complexidade de tempo e espaço.
Mais importante do que saber que um int tem 28 bytes é avaliar se o programa atende aos requisitos de desempenho e, se não atender, procurar um algoritmo melhor
Por exemplo, o fato de concatenação de strings ser O(n²) também influencia o design de f-strings no Python.
Dicionários são amplamente usados em Python porque são rápidos, pelo mesmo motivo.
Esses números servem para justificar em números esse conhecimento implícito
Isso me lembra o texto sobre os problemas que Eric Raymond encontrou ao migrar o GCC com o Reposurgeon
O título é confuso, mas na verdade é uma paródia do artigo de 2012 do Jeff Dean, “Latency Numbers Every Programmer Should Know”.
Esse tipo de brincadeira com títulos é comum em artigos de CS
Era material interno para o projeto da arquitetura de RAM vs Disk do mecanismo de busca inicial do Google.
Depois, com a chegada da memória flash, os números mudaram, e há até a história de que Jeff criou um algoritmo de compressão para servir dados genômicos diretamente a partir de flash
A maioria dos desenvolvedores Python deveria focar em coisas mais importantes do que esses detalhes de desempenho de baixo nível.
Esse tipo de material é bom como referência, mas na prática raramente é necessário
A explicação sobre o tamanho das strings está errada. Em Python existem três tipos de string que usam 1, 2 ou 4 bytes por caractere.
Veja mais detalhes neste blog
O título e os exemplos do texto são um pouco imprecisos.
Por exemplo, dizer que “item in set é 200 vezes mais rápido que item in list” fala sobre teste de pertencimento, não sobre comparação de velocidade de iteração.
Ainda assim, no geral, o formato e a organização são atraentes
Falta medir o tempo de criação de instâncias de classe.
Depois de um refactor no meu código, troquei uma estrutura simples de listas por classes e o tempo de execução passou de alguns microssegundos para alguns segundos.
Seria bom medir casos assim
O problema pode ser abuso de classes. Às vezes uma estrutura simples de listas é melhor
É mais provável que tenha havido uso incorreto de orientação a objetos.
Vale postar o código no StackOverflow ou no CodeReview.SE para receber feedback
Li este texto achando interessante pela perspectiva de “será que há algo fundamentalmente errado com o Python moderno”.
Mas não concordo com a ideia de que todo mundo “deveria saber” esses números.
Basta ter uma noção intuitiva de algumas operações principais
O intervalo de cache de small int em Python não é de 0 a 256, mas de -5 a 256.
Por causa disso, iniciantes frequentemente confundem identidade (
is) com igualdade (==)