Bun vs Node.js: Um Experimento de Teste de Carga

Desde o seu lançamento, o Bun tem se destacado como uma solução promissora para desenvolvedores que buscam otimizar o desempenho das suas aplicações JavaScript. Recentemente, há um interesse crescente em utilizar o Bun em ambientes de produção.

Com isso em mente, realizar um exame prático para avaliar as vantagens de usar o Bun agora parece uma boa ideia. Hoje, vamos realizar um experimento simples de teste de carga para comparar o desempenho de uma API Express.js rodando em dois runtimes JavaScript distintos: Node.js e Bun.

Nossa análise se concentrará na avaliação de desempenho de um endpoint responsável por calcular números da sequência Fibonacci sob um alto volume de requisições.

Vamos lá!

O Código

Você pode acessar o código usado para esses testes nos links abaixo:

O mesmo código será usado para ambos os ambientes de runtime.

Configurando no zCloud

Em seguida, vamos proceder com o deploy de ambas as APIs no zCloud.

Felizmente, configurar e implantar aplicações no zCloud é fácil.

Conforme ilustrado na imagem abaixo, implantar um aplicativo simplesmente envolve selecionar os recursos computacionais desejados, definir o nome do aplicativo, escolher a região de deploy e definir as configurações relacionadas ao Docker, como o caminho do Dockerfile do seu projeto.

configure-deploy-1.png

configure-deploy-2.png

Com a configuração pronta, é hora de submeter esses endpoints a testes de carga pesados.

Disparando

Vou utilizar o Autocannon, uma ferramenta de benchmarking HTTP escrita em Node.js, para executar o teste de carga. Usaremos o seguinte comando:

npx -y autocannon -c 25 -w 4 -d 60 <URL>
  • A flag c determina o número de conexões simultâneas.
  • w especifica o número de threads de trabalho.
  • Por último, d define a duração do teste do autocannon em segundos.

Este comando resultará em duas tabelas contendo dados sobre as requisições:

Bun:

bun-autocannon-table.png

Node.js:

nodejs-autocannon-table.png

Agora, vamos comparar os resultados.

O Bun exibiu uma latência média de 387,36 milissegundos, atingindo um pico de 3357 milissegundos. Em contraste, o Node.js registrou uma latência média de 1112,88 milissegundos, com um pico de 3608 milissegundos.

A tabela seguinte se concentra nas estatísticas das requests. O Bun processou com sucesso cerca de 3.700 requisições, alcançando uma média de 61 requisições por segundo. Enquanto isso, o Node.js gerenciou cerca de 1.700 requisições, com uma média de cerca de 21 requisições por segundo.

Também podemos comparar as métricas usando o Grafana do zCloud. Você pode acessar os Dashboards através do link disponível na página de App Env.

dashboards-dropdown.png

Comparação

Vamos começar avaliando o uso de recursos físicos: CPU e memória.

Enquanto o Node.js utilizava totalmente a CPU alocada para o aplicativo (0.5 zCloud), o Bun atingiu apenas 60% desse valor:

Bun:

cpu-usage-bun.png

Node.js:

cpu-usage-node.png

Em termos de uso de memória, o Bun consumiu quase a metade da memória em comparação com o Node.js:

Bun (~50MiB):

memory-usage-bun.png

Node.js (~80MiB):

memory-usage-node.png

Em seguida, vamos analisar os resultados das requisições no Dashboard do Ingress.

Conforme ilustrado no gráfico abaixo, a API do Bun demonstrou a capacidade de transmitir mais de 20 KiB/s de resposta, enquanto o Node.js atingiu apenas 8 KiB/s:

ingress-reponse-size-edited.png

Além disso, a tabela abaixo categoriza as métricas de solicitação por path e response status.

Bun:

ingress-table-bun.png

Node.js:

ingress-table-node.png

O Bun lidou com quase o triplo de requisições em comparação com o Node.js, com um tempo médio de resposta menor:

3704 / 1273 ≃ 2.9

Além disso, em relação às requisições malsucedidas, o Bun exibiu uma taxa de falha de 0.4% com códigos de erro 499, enquanto o Node.js encontrou uma taxa de falha de 1.7%:

Bun:

(15 * 100) / (3704 + 15) ≃ 0.4

Node.js:

(22 * 100) / (1273 + 22) ≃ 1.7

Conclusão

Em conclusão, o Bun mostrou métricas de desempenho superiores em comparação com o Node.js, exibindo maior throughput de requisições, menor latência e utilização de memória mais eficiente.

É crucial enfatizar que essas descobertas são derivadas de testes de benchmarking de um endpoint simples. Não estamos incentivando o abandono do Node.js, nem estamos implicando sua inferioridade. Em vez disso, esses resultados fornecem informações objetivas a serem consideradas.

Além disso, o recurso de observabilidade do zCloud se mostrou excepcional. Não requer configuração adicional, proporcionando um eficiente e valioso monitoramento dos seus apps.

Não investigamos por que o Bun se saiu muito melhor numa tarefa vinculada à CPU. Se você souber explicar essa diferença, deixe um comentário abaixo com mais detalhes.


Post original (em inglês) Assista também o vídeo abordando tudo que foi falado no post.


Bom demais Joao, ficou bem massa o post! Parabéns.

Seria top alguém conseguir explicar essa diferença.

Posso estar enganado, mas acredito que o resultado é ainda mais evidente quando usado agum framework feito para o Bun, com as APIs criadas para ele. Apesar de ele ter praticamente 100% de compatibilidade com as APIs do Node, as APIs dele são ainda mais poderosas.

Achei as latências bem altas e as RPS bem baixas, mas creio que pode ser pela máquina/conexão utilizada nos testes. Já que foi comparado desempenho de APIs, eu fiquei curioso para saber a comparação do Express/Bun com frameworks Python assíncronos geralmente utilizados para APIs, como FastAPI, LiteStar e Sanic. Eu encontrei um artigo comparando Express vs FastAPI: https://medium.com/deno-the-complete-reference/express-vs-fastapi-hello-world-performance-c6d18b0368e4 Mas estes testes com "Hello World" são muito simples. Gostaria de ver os frameworks Javascript vs LiteStar que consegue ter desempenho ainda melhor que FastAPI: https://docs.litestar.dev/2/benchmarks.html

Pessoalmente acho que não tenha muita relação comparar JS com Python. Se fosse assim, para ser justo, teria que incluir Rust, Go, Zig, Nim, Pascal, C#, Java, etc. na comparação.
Por isso eu comecei falando "Já que foi comparado desempenho de APIs..." e citei alguns frameworks Python voltados mais para APIs.

Interessante, por mais comparações iguais essa. Foi incrível como você apresentou a diferença

  1. Acho que poderias incluir Deno na comparação.
  2. Não é a minha área mas acho que é difícil explicar. Talvez tenha dicas na documentação do Bun. Talvez sejam os algorítmos utilizados. Talvez seja a linguagem utilizada. Provavelmente todos e mais alguma coisa.

Seria interessante realizar esse teste também utilizando o Fastify em ambos! O Fastify é muito mais rápido e eficiente que o express, provavelmente vai extrair muito mais da performance do Bun, do que o express.

Curti bastante o experimento, principalmente por comparar tecnologias atuais o bun tem bastante potencial, ja faço algums testes com o elysia para testar a performance