O que é mais rápido: Go, Python, Node, Deno ou Bun?

Geekie Educação
Geekie Educação
Published in
9 min readJul 2, 2024
Velocímetro indicando um aumento de velocidade
Fonte: Shutterstock

A motivação por trás deste artigo é a crescente popularidade de novas runtimes de JavaScript e TypeScript, como Deno e Bun, que prometem melhorias significativas em desempenho e segurança em relação ao Node tradicional. Considerando nossa experiência e interesse em vários frameworks e linguagens de programação, decidimos aproveitar a oportunidade para comparar essas novas runtimes com outras linguagens que também são muito utilizadas no desenvolvimento de aplicações web como Python e Go.

Python, amplamente utilizado pela sua simplicidade e vasto ecossistema de bibliotecas, é muitas vezes criticado por seu desempenho em cenários de alta carga. Go, por outro lado, é reconhecido por sua eficiência e performance robusta, mas pode ser menos acessível para desenvolvedores(as) iniciantes. A comparação entre essas linguagens e frameworks nos permitirá não apenas observar as diferenças de performance, mas também discutir os trade-offs em termos de facilidade de uso, comunidade e ecossistema.

Nosso objetivo com este artigo é fornecer uma análise detalhada e imparcial, apresentando os resultados dos testes de performance de diferentes runtimes e frameworks. Dessa forma, pretendemos contribuir para que desenvolvedores(as) e equipes de tecnologia possam tomar decisões mais informadas na escolha das ferramentas que melhor atendam às suas necessidades específicas.

Para alcançar esse objetivo, utilizamos o processo de benchmarking de performance. Benchmarking de performance é a prática de medir e comparar o desempenho de diferentes sistemas, aplicações ou componentes. O objetivo é identificar quais soluções são mais eficientes em termos de velocidade, uso de recursos e capacidade de resposta. Esse processo envolve a execução de testes padronizados que geram métricas quantitativas, permitindo uma análise objetiva das capacidades de cada sistema.

No contexto deste artigo, consideramos que a prática de benchmarking é essencial para guiar decisões sobre qual tecnologia adotar, especialmente quando se busca otimizar a performance de aplicações web ou microsserviços.

Preparação do ambiente

Para garantir a consistência e a precisão do nosso benchmarking de performance, configuramos nosso ambiente utilizando duas instâncias AWS EC2 t3.micro em uma rede privada, uma para servir a aplicação a ser avaliada e outra para executar o teste de carga. Essa configuração foi escolhida para mitigar oscilações de rede e padronizar o hardware.

As instâncias EC2 possuem 2 vCPUs e 1 GB de RAM, rodando Amazon Linux 2023 (versão 6.1.92–99.174.amzn2023.x86_64).

Versão das linguagens e runtimes utilizadas:

  • Go: 1.22.4
  • Node: 20.14.0
  • Deno: 1.44.1
  • Bun: 1.1.13
  • Python: 3.9.16

Versões dos frameworks utilizados:

  • Fiber: 2.52.4
  • Gin: 1.10.0
  • Express: 4.19.2
  • Hono: 4.4.4
  • Fastify: 4.27.0
  • Flask: 3.0.3 (com Gunicorn 22.0.0)
  • FastAPI: 0.111.0 (com Uvicorn 0.30.1)

Para a execução dos testes de carga, utilizamos a ferramenta ApacheBench v2.3.

Implementamos três rotas nas APIs:

  1. GET / que retorna {message: 'ok'}.
  2. GET /fib/:num que executa o cálculo do número de Fibonacci de forma recursiva e retorna {result: number}.
  3. POST / que recebe um objeto JSON e retorna o mesmo objeto para avaliar o desempenho do parsing de JSON.

Comandos utilizado para os testes de carga:

  • ab -n 10000 -c 100 http://<api-host>:3000/
  • ab -n 10000 -c 100 http://<api-host>:3000/fib/20
  • ab -p post-object.txt -T application/json -n 10000 -c 100 http://<api-host>:3000/

Para o Flask, utilizamos Gunicorn como servidor WSGI (como indicado na documentação), executando com o seguinte comando:

gunicorn -w 4 -b 0.0.0.0:3000 'main:app'

Para o FastAPI, executamos no modo de produção com o seguinte comando:

fastapi run main.py

Observações

Durante a execução dos nossos benchmarks, tomamos várias precauções e fizemos alguns ajustes importantes para garantir a assertividade e a imparcialidade dos resultados. Primeiramente, desativamos os logs de todas as execuções para evitar qualquer impacto negativo na performance, pois os logs podem introduzir uma sobrecarga significativa, especialmente em testes de carga. Nossa prioridade é medir o desempenho puro das linguagens e frameworks sem interferências adicionais.

Em um dos testes, executamos o Flask diretamente do Python, sem o uso de um servidor WSGI. Fizemos isso apenas por curiosidade, já que essa não é a prática recomendada para produção. Para uma comparação mais justa e alinhada com as melhores práticas, também executamos o Flask utilizando Gunicorn, conforme a documentação oficial: Deploying Flask with Gunicorn. Gunicorn é um servidor WSGI amplamente utilizado que oferece melhor performance e manejo de concorrência.

Para o FastAPI, utilizamos o Uvicorn como o servidor WSGI padrão, seguindo as recomendações oficiais: Deploying FastAPI with Uvicorn. Uvicorn é um servidor ASGI que suporta conexões assíncronas, proporcionando alta performance e escalabilidade para aplicações FastAPI.

Essas configurações foram escolhidas para garantir que cada framework fosse testado em condições otimizadas, refletindo cenários de uso reais e recomendados, permitindo uma comparação mais justa entre as diferentes tecnologias.

Resultados

Para cada combinação de stack e rota implementada, foram executados 5 testes de carga a partir da ferramenta Apache Bench com os comandos citados acima, basicamente simulando 10000 requisições através de 100 conexões simultâneas.

Tempo de resposta

Os gráficos abaixo, apresentam o tempo de resposta médio de cada stack em cada rota em milissegundos, considerando todas as execuções efetuadas. Portanto, valores menores nas colunas demonstram as melhores performances neste quesito.

1.1 — Tempo de resposta médio de todas as stacks na rota GET /
1.1 — Tempo de resposta médio de todas as stacks na rota GET /
1.2 — Tempo de resposta médio de todas as stacks na rota GET /fib/20
1.2 — Tempo de resposta médio de todas as stacks na rota GET /fib/20
1.3 — Tempo de resposta médio de todas as stacks na rota POST /
1.3 — Tempo de resposta médio de todas as stacks na rota POST /

Percentil

Os gráficos abaixo demonstram os percentis de tempo de resposta: p50, p75, p90 e p100, representando os tempos de resposta abaixo dos quais 50%, 75%, 90% e 100% das requisições foram atendidas, respectivamente. Estas métricas são importantes para entendermos a distribuição dos tempos de resposta e constatarmos a variabilidade e resiliência do stack em questão.

Em outras palavras, um p50 no valor de 10, por exemplo, significa que 50% das requisições tiveram o tempo de resposta igual ou inferior à 10 milissegundos. Já um p100 de 200, por exemplo, significa que a requisição mais demorada do teste de carga levou 200 milissegundos, pois 100% das requisições tiveram o tempo de resposta igual ou inferior a esse valor.

Como executamos 5 testes de carga para cada combinação de stack e rota, estamos considerando a média de cada faixa de percentil.

2.1 — Percentil médio de todas as stacks na rota GET /
2.1 — Percentil médio de todas as stacks na rota GET /
2.2 — Percentil médio de todas as stacks na rota GET /fib/20
2.2 — Percentil médio de todas as stacks na rota GET /fib/20
2.3 — Percentil médio de todas as stacks na rota POST /
2.3 — Percentil médio de todas as stacks na rota POST /

Insights

Performance geral

  • Go (com Fiber e Gin) e Bun (com Hono) demonstraram as melhores performances gerais em termos de requisições por segundo (RPS) e tempo de resposta médio nas rotas GET e POST.
  • Em particular, Go com Fiber e Bun com Hono se destacaram significativamente, mostrando que Go e Bun podem ser escolhas interessantes para aplicações de alta performance. Deno com Hono ficou muito próximo, então também precisa ser considerado.
3.1 — Comparativo dos melhores tempos de resposta médio na rota GET /
3.1 — Comparativo dos melhores tempos de resposta médio na rota GET /

Resiliência à cargas pesadas

  • Go manteve uma performance consistente e baixo tempo de resposta, mesmo sob altas cargas, tanto em operações simples (GET /) quanto mais complexas (GET /fib/20).
  • Bun também apresentou resiliência notável. Comparando com o seu par mais próximo, performou melhor que o Deno nas rotas GET / e GET /fib/20, porém perdeu um pouco no p100 para o Deno na rota POST.
4.1 — Comparativo das menores variabilidades de tempo de resposta na rota GET /
4.1 — Comparativo das menores variabilidades de tempo de resposta na rota GET /
4.2 — Comparativo das menores variabilidades de tempo de resposta na rota GET /fib/20
4.2 — Comparativo das menores variabilidades de tempo de resposta na rota GET /fib/20
4.3 — Comparativo das menores variabilidades de tempo de resposta na rota POST /
4.3 — Comparativo das menores variabilidades de tempo de resposta na rota POST /

Comparação entre frameworks de JavaScript/TypeScript

  • Express mostrou-se significativamente menos eficiente em comparação com frameworks mais novos como Hono e Fastify.
  • De modo geral, entre os frameworks testados, Hono se mostrou como sendo o framework mais otimizado em termos de performance.
5.1 — Comparativo do tempo de resposta médio dos frameworks de JavaScript/TypeScript na rota GET /
5.1 — Comparativo do tempo de resposta médio dos frameworks de JavaScript/TypeScript na rota GET /
5.2 — Comparativo do percentil médio dos frameworks de JavaScript/TypeScript na rota GET /
5.2 — Comparativo do percentil médio dos frameworks de JavaScript/TypeScript na rota GET /

Impacto da linguagem ou runtime

  • Sem sombra de dúvidas, dentre as linguagens e runtimes avaliados, Go se mostra como sendo o stack mais performático.
  • Dentro do ecossistema de JavaScript/TypeScript, Bun consistentemente superou Node e Deno com os mesmos frameworks, também destacando-se como uma runtime altamente eficiente.
  • Flask e FastAPI apresentaram os maiores tempos de resposta, sugerindo que frameworks em Python podem não ser a melhor escolha para aplicações que exigem alta performance.
6.1 — Comparativo dos melhores e piores tempos de resposta médio na rota GET /
6.1 — Comparativo dos melhores e piores tempos de resposta médio na rota GET /
6.2 — Comparativo dos melhores e piores tempos de resposta médio na rota GET /fib/20
6.2 — Comparativo dos melhores e piores tempos de resposta médio na rota GET /fib/20
6.3 — Comparativo dos melhores e piores tempos de resposta médio na rota POST /
6.3 — Comparativo dos melhores e piores tempos de resposta médio na rota POST /

Variabilidade nos tempos de resposta

  • Analisando os percentis, Go mostrou menor variabilidade e menores tempos de resposta, enquanto Python apresentou maior variabilidade e tempos de resposta mais altos.
7.1 — Comparativo das menores e maiores variabilidades no tempo de resposta na rota GET /
7.1 — Comparativo das menores e maiores variabilidades no tempo de resposta na rota GET /

Rota POST

  • Bun com Hono e Go com Fiber apresentaram as melhores performances para operações POST. Isso sugere que ambos sejam altamente eficientes em operações de parsing.
8.1 — Comparativo dos melhores tempos de resposta médio na rota POST /
8.1 — Comparativo dos melhores tempos de resposta médio na rota POST /

Análises e experiências

A análise de performance realizada neste estudo forneceu insights valiosos sobre os diferentes runtimes e frameworks utilizados. No entanto, é fundamental destacar que a performance, embora crucial, não deve ser o único fator considerado na escolha de uma tecnologia para projetos de desenvolvimento. Outros requisitos, como a experiência e produtividade dos desenvolvedores, a integração com outras tecnologias do stack, a facilidade de manutenção e a escalabilidade, também são aspectos importantes que devem ser levados em conta.

Dito isso, nossa experiência com Deno foi particularmente satisfatória, especialmente devido ao modo como as dependências são gerenciadas. Tanto o Bun quanto o Deno mostraram performance superior em comparação com o Node, além da capacidade de compilar TypeScript em tempo de execução, proporcionando uma experiência de desenvolvimento mais ágil e eficiente. No entanto, vale ressaltar que tanto o Deno quanto o Bun são runtimes muito mais novas do que o Node. Portanto, é importante ter cautela ao optar por utilizá-las caso seu projeto requeira recursos mais avançados ou menos comuns, que podem ainda não estar tão maduros.

Trabalhar com Go foi uma experiência muito positiva. A linguagem oferece uma sintaxe limpa e padronizada, e frameworks como Gin e Fiber facilitam bastante o desenvolvimento de aplicações web. A simplicidade e a performance do Go se destacaram, tornando-o uma excelente escolha para aplicações que requerem alta performance. Um ponto de atenção em relação ao Go é que, em comparação com os ecossistemas de TypeScript/JavaScript ou Python, existem menos pacotes e ferramentas disponíveis, devido à sua relativa novidade e à cultura de “faça você mesmo”, que busca manter as dependências mínimas e a performance máxima.

De modo geral, os resultados indicam que a escolha da linguagem, runtime e framework pode impactar significativamente a performance de aplicações web. No que se refere a linguagens e runtimes, Go e Bun se destacam como líderes em termos de baixo tempo de resposta, enquanto frameworks modernos como Hono e Fastify apresentam vantagens claras sobre opções mais tradicionais como Express e Flask. Portanto, em cenários onde a performance pode ser um diferencial competitivo, a escolha adequada de tecnologia se mostra crucial.

Conclusão

Para concluir este estudo, é essencial destacar que a escolha da “tecnologia ideal” deve equilibrar performance e outros fatores cruciais para o sucesso do projeto. A decisão final deve sempre considerar desempenho, produtividade, facilidade de uso e compatibilidade com o ecossistema tecnológico existente.

À medida que novas tecnologias surgem e evoluem, é crucial reavaliar constantemente as ferramentas utilizadas. Será que os frameworks e runtimes que utilizamos hoje ainda serão os mais adequados amanhã? Como podemos nos preparar para adotar novas tecnologias que possam surgir e oferecer ganhos de performance e escalabilidade? Essas questões nos levam a refletir sobre a importância da arquitetura de software, pois a forma como organizamos nossos sistemas pode facilitar ou dificultar a adoção de novas tecnologias ao longo do tempo.

A pesquisa sugere que estar aberto a experimentar e adotar soluções inovadoras é fundamental para se manter à frente em um ambiente tecnológico em constante evolução. Os insights apresentados aqui são apenas um ponto de partida. A pergunta que fica é: quais outras combinações de tecnologias poderiam oferecer melhorias ainda maiores? Com isso em mente, esperamos que as análises e observações deste artigo sejam úteis e sirvam como base para decisões mais informadas e estratégicas.

Escrito por: Rafael Ponciano e Osvaldo Zonetti.

--

--