38 pontos por GN⁺ 2026-01-02 | 1 comentários | Compartilhar no WhatsApp
  • 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

 
GN⁺ 2026-01-02
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

    • Eu trabalho com otimização de desempenho de sistemas escritos em Python. Mas esses números não significam muita coisa até virarem um problema real. Quando isso acontece, eu meço diretamente. Se você escreve código tentando economizar chamadas de método, acaba perdendo as vantagens do Python
    • Python é lento até em operações básicas. É lento em tarefas simples como chamadas de função ou acesso a dicionário. Na verdade, Python sobreviveu graças às bibliotecas baseadas em C/C++ (como Numpy)
    • Esses números não são um problema exclusivo de Python. Em Zig também se considera ciclos de CPU e cache misses. Toda linguagem tem latência para certas operações. Pode haver motivos para não usar Python, mas esse não é um deles
    • Algumas operações melhoram usando módulos alternativos. É importante conhecer esse tipo de coisa, mas quem realmente precisa disso provavelmente já sabe. Ainda assim, Python é uma linguagem excelente para prototipagem
    • Nosso sistema de build também é feito em Python, então quero manter Python mesmo melhorando a performance. Por isso, esses números são muito importantes
  • Paradoxalmente, no momento em que esses números passam a importar, Python já não é a ferramenta certa para esse trabalho

    • Uma abordagem realista é manter o código em Python e mover só as partes críticas de desempenho para extensões em C ou Rust. É assim que numpy, pandas e PyTorch funcionam.
      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
    • Trabalho com Python há 20 anos, mas nunca precisei conhecer esses números. Em vez disso, resolvo com profiling e ferramentas como Cython, SWIG e JIT
    • Se a aplicação é sensível a ponto de esses números importarem, então Python é uma linguagem de nível alto demais para otimizar com eficiência
    • Mas eu já construí grandes pipelines de dados em Python. A combinação turbodbc + pandas entrega velocidade de nível C++. Usa mais memória, mas considerando custo de mão de obra, é muito mais eficiente.
      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
    • Esses números são o último recurso. Só entram em consideração depois de resolver gargalos mais comuns, como I/O de disco, rede e complexidade algorítmica
  • 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

    • Mas desempenho é sempre uma abstração que vaza. Quer percebamos ou não, ele afeta o código inteiro.
      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
    • O fato de um int ter 28 bytes realmente importa em problemas que exigem criar grandes quantidades de objetos.
      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

    • Se alguém escrevesse um artigo com o título “latency numbers considered harmful is all you need”, provavelmente faria sucesso na academia
    • Mas parece que o autor deste texto estava falando sério. Não foi culpa dos leitores interpretarem o título dessa forma
    • Para o título fazer sentido, os números precisariam ser realmente úteis, mas são muitos e pouco práticos
    • Como referência, o texto original do Jeff Dean parece ter sido escrito muito antes de 2012.
      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

    • Mas conhecimento geral sobre as ferramentas que você usa sempre tem valor. É capital intelectual e, em certas situações, ajuda bastante
    • Quando você bate no limite, basta procurar um módulo implementado em C ou escrever um você mesmo. Foi assim que Python evoluiu desde o começo
    • Eu também sempre trabalhei com uma noção de “rápido o suficiente” na maioria dos casos. Este material ajuda a confirmar com números essa intuição
  • 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

    • Isso me lembra a piada do médico: “Dói quando faço isso” / “Então não faça isso”.
      O problema pode ser abuso de classes. Às vezes uma estrutura simples de listas é melhor
    • A criação de instâncias de classe em si normalmente não é um problema de desempenho.
      É 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 (==)

    • Java também tem um comportamento parecido. Para iniciantes, isso pode ser confuso