[DESAFIO] NodeJS mais rápido que o Bun v1.0.1

Uma nova runtime de Javascript foi lançada esse mês o Bun e provavelmente tu já sabe. Eu sempre testo performance usando algoritmo basico e assim como fiz pro Mojo vs. Python escolhi fazer Fibonacci.

Eis que o Bun ficou MUITO atras do NodeJS: Video nesse Tweet.

Me ajude a descobrir onde está a perda de performance do Bun.

console.time("Execution Time");
const n = 1000000
let a = BigInt(0), b = BigInt(1); // fixed
for (let i = BigInt(0); i < n; i++) {
  [a, b] = [b, a + b];
}
console.log("fib: " + a);
console.timeEnd("Execution Time");

Adicionando misterio a essa desafio:

Se fizer o loop a seguir sem a matematica do Fibonacci o Bun desempenha melhor que o NodeJS.

console.time("Execution Time");
const n = 1000000
for (let i = BigInt(0); i < n; i++) {
    console.log("Item: " + i);
}
console.timeEnd("Execution Time");

Ambiente que rodei o teste MacOS

  • Apple Chip M1
  • Apple Chip M1 Max

Nota: Obviamente rodei o mesmo codigo em ambos e não usei nenhuma biblioteca externa apenas o raw Javacript que o DHH tanto ama.

Fiz alguns testes aqui, só fiz uma pequena modificação no código, inicializando o n com um BigInt também:

let n = 1000000n; // sufixo "n" faz com que seja BigInt
let a = BigInt(0), b = BigInt(1);
for (let i = BigInt(0); i < n; i++) {
  [a, b] = [b, a + b];
}
console.log(a);

Testei na minha máquina (Ubuntu 22.04.3, processador 11th Gen Intel® Core™ i5-1145G7 2.60GHz, 8 núcleos), primeiro com o teste mais básico, usando o comando time do Linux. Ou seja, time bun run arquivo.js e time node arquivo.js. A versão do Node é v18.17.1, e do Bun é 1.0.1. Os resultados estão abaixo (lembrando que em outros hardwares os tempos poderão ser diferentes, obviamente), só retirei a saída do console.log para não deixar poluído.

Node:

real    0m10,368s
user    0m8,712s
sys     0m0,037s

Bun:

real    0m9,915s
user    0m9,782s
sys     0m1,833s

O que importa é a primeira linha ("real", o tempo que efetivamente se passou entre eu teclar ENTER e o comando terminar).

Só de curiosidade, "user" é o tempo de CPU em user mode (fora do kernel), e "sys" é o tempo de CPU dentro do kernel (em chamadas de sistema, por exemplo). Mas estes tempos são computados considerando todos os núcleos, então se a máquina tem mais de um, a soma de "user" e "sys" pode ser maior que "real", conforme explicado aqui.

Enfim, no meu caso o Node demorou um pouco mais, mas dado o tempo total de ambos (10,3 segundos versus 9,9 segundos), não acho que foi uma diferença tão significativa (cerca de 4% a mais).


Mas tem um detalhe, operações de I/O (como o console.log) costumam ser caras e elas geralmente acabam mascarando o resultado (neste caso nem tanto porque só tem uma, mas enfim). Então removi o console.log para ver apenas o tempo do loop e rodei de novo.

Node:

real    0m8,615s
user    0m8,577s
sys     0m0,033s

Bun:

real    0m9,329s
user    0m9,424s
sys     0m1,710s

Agora o Bun foi ligeiramente mais lento, mas novamente, não acho que a diferença é tão significativa.

Testei mais algumas vezes, e às vezes o Node era mais rápido, às vezes era mais lento, mas sempre com diferenças pequenas. Nada tão gritante quanto o que vc encontrou.


Uma maneira melhor de testar

O problema de fazer um teste isolado é justamente esse: vc roda uma vez um pequeno trecho de código e já acha que é o suficiente, mas conforme já visto aqui, isso pode levar a conclusões precipitadas.

Mesmo que rode várias vezes, podem ter outros fatores que influenciam, como outros processos rodando na mesma máquina (ainda mais se tiver I/O e outras operações bloqueantes), e até mesmo a própria inicialização do runtime (tanto o Node quanto o Bun precisam de uma etapa de inicialização antes de começar a rodar o código propriamente dito).

Sendo assim, uma forma melhor de testar seria usar uma lib específica que desconsidera esses fatores externos. Eu usei o Benchmark.js, o código ficou assim:

var Benchmark = require('benchmark');
var suite = new Benchmark.Suite;

suite.add('test', function () {
    let n = 1000000n;
    let a = BigInt(0), b = BigInt(1);
    for (let i = BigInt(0); i < n; i++) {
      [a, b] = [b, a + b];
    }
}).on('cycle', function (event) {
    console.log(String(event.target));
}).run({
    'async': true
});

Obs: o propósito do Benchmark.js na verdade é comparar dois códigos diferentes. Por exemplo, chamo várias vezes add com algoritmos diferentes, e no final ele mostra qual é mais rápido. Mas aqui eu coloquei apenas um, e rodei este teste no Node e depois no Bun, pois ele também mostra a quantidade de operações por segundo que conseguiu executar. E é exatamente isso que eu quero comparar, pois dá para ter uma ideia melhor do desempenho de cada um.

Resultados com Node:

test x 0.12 ops/sec ±0.24% (5 runs sampled)

E com Bun:

test x 0.11 ops/sec ±0.90% (5 runs sampled)

Ou seja, 0,12 operações por segundo versus 0,11 operações por segundo. Em outras palavras, no Node demoraria cerca de 8,3 segundos para rodar o código uma vez, e no Bun, cerca de 9 segundos. Mais uma vez, uma diferença bem pequena, nada da discrepância que vc encontrou.

Rodei mais algumas vezes e os resultados giraram em torno disso, com pouca variação, e às vezes um era melhor, às vezes o outro - o que mostra que mesmo uma lib que desconsidera fatores externos não consegue eliminar 100% deles.


Quanto ao segundo caso (o console.log dentro do loop), realmente o Node foi pior em todos os casos. Primeiro com time, segue abaixo.

Bun:

real    0m2,648s
user    0m0,656s
sys     0m1,416s

Node:

real    0m5,264s
user    0m4,276s
sys     0m0,884s

E com o Benchmark.js:

Bun:

test x 0.38 ops/sec ±2.40% (5 runs sampled)

Node:

test x 0.19 ops/sec ±7.30% (5 runs sampled)

Ou seja, o Bun foi cerca de duas vezes mais rápido, uma diferença maior que no primeiro código.

Pesquisei um pouco a respeito e encontrei isso, que indica que a implementação do console.log no Node acaba deixando-o pior para este caso específico.


Conclusões (ou não)

No fim das contas, é difícil tirar conclusões definitivas sobre qual deles sempre será mais rápido. Isso depende de tantos fatores que o melhor a fazer é testar em uma situação mais próxima possível do caso concreto: teste o seu sistema, com seu código e seus casos de uso, e veja se faz diferença. Testar rodando um código qualquer uma ou poucas vezes é o teste mais ingênuo e propenso a erros que vc pode fazer (e tirar "verdades" disso é pior ainda). Testar a situação real costuma ser mais efetivo, pois aí vc está considerando o seu contexto específico em vez de só seguir a moda. Mais ainda, como vimos acima, em um caso particular pode fazer mais diferença que em outros, então o melhor é testar com o código que vc efetivamente vai usar.

Se procurar por benchmarks que comparam os dois, vai encontrar vários diferentes, e é importante ver qual código foi usado para testar, já que isso pode fazer diferença. E claro que também precisa considerar outros fatores: se usar A ou B vai facilitar o dia-a-dia da sua equipe, se é estável, se aguenta o tranco, etc etc etc. Vc pode inclusive concluir que não faz diferença, e não tem problema, pois o que importa é que seja uma decisão embasada, em vez de só puro achismo ou "ouvi dizer que é melhor".

Exato! Foi um typo porque eu reescrevi o Fibonacci enquanto postava aqui :) `b = BigInt(1)` **PRIMEIRAMENTE: MUITO MASSA A TUA ANALISE!** Eu vou postar um video amanhã sobre e vou colocar o link do TabNews nele porque acredito que até amanhã conseguimos chegar na conclusão do porque o NodeJS performa muito mais rapido que o Bun (todas as vezes aqui) no meu ambiente com um **MacOS M1 Max**. Não usei nenhuma biblioteca, apenas usei de fato o `console.log` e um `console.time`. Eu vou rodar o `Benchmark.Suite;` aqui e postar como que ficou no **M1**.
O resultado aqui no **M1 Max** foi bem diferente. ``` rinha % bun execute.js test x 3.80 ops/sec ±5.63% (14 runs sampled) rinha % node execute.js test x 12.14 ops/sec ±3.39% (35 runs sampled) ``` Rodei multiplas vezes em todas o bun ficava em 3-4 ops/sec enquanto o Node ficava em 11-12 ops/sec, confirmando que no meu **M1 Max** rodando o mesmo teste que você o Node performou 3x-4x mais rapido que bun. Seria interessante alguem com **Apple Chip M1** fazer o mesmo testes, e talvez alguem com MacOs Intel Chip.
Eu tenho um MacBook antigo (2012), processador 2.6 GHz Quad-Core Intel Core i7, e refiz o teste nele com as mesmas versões (Node 18.17.1 e Bun 1.0.1). Agora a diferença foi bem maior no código que calcula Fibonacci. Primeiro com `time`: Node: ``` real 0m9.818s user 0m9.412s sys 0m0.443s ``` Bun: ``` real 0m33.275s user 0m33.062s sys 0m3.324s ``` E com o Benchmark.js: Node: ``` test x 0.14 ops/sec ±1.67% (5 runs sampled) ``` Bun: ``` test x 0.03 ops/sec ±0.36% (5 runs sampled) ``` --- Já com o segundo código (`console.log` dentro do *loop*), a situação se inverte e o Bun se mostra mais rápido. Primeiro com `time`: Node: ``` real 0m12.736s user 0m10.322s sys 0m1.732s ``` Bun: ``` real 0m5.140s user 0m2.289s sys 0m1.335s ``` E com o Benchmark.js: Node: ``` test x 0.09 ops/sec ±0.15% (5 runs sampled) ``` Bun: ``` test x 0.21 ops/sec ±0.15% (5 runs sampled) ``` Provavelmente por causa [do que já mencionei](https://stackoverflow.com/q/6853566), de que a implementação do `console.log` no Node piora bastante a performance. No primeiro código não causa problema porque é só uma chamada no final e o grosso do trabalho é no cálculo, mas no segundo já faz diferença. --- Curiosamente, se não usar `BigInt`, aí muda de novo: ```javascript // diminuí o n para não estourar o valor máximo de Number let n = 1000; let a = 0, b = 1; for (let i = 0; i < n; i++) { [a, b] = [b, a + b]; } ``` Usando este código, o Bun foi **muito** mais rápido que o Node, tanto no Mac quanto no Linux (em média cerca de 10 vezes mais rápido).
só que usando esse codigo tu nao chega em `n` de 100.000 imagina 1.000.000 haha BigInt parece ser uma limitação do bun atual, sobre o `console.log` se nao me engano ele vai direto pra API nativa no V8, provavelmente aqui temos uma diferença do Zig no bun?
Bom, a ideia era eliminar o uso de `BigInt` pra ver se mudava alguma coisa, então o jeito foi diminuir o valor. Mas se somente o `BigInt` fosse o problema, então daria diferença no Linux também, mas aqui deu "empate técnico". Talvez seja a combinação `BigInt` + Mac que cause essa perda de desempenho, fica aí a questão pra uma futura investigação :-) Também pensei se a desestruturação faz alguma diferença, fica aí outra sugestão de teste também (usar atribuições simples em vez de `[a, b] = [b, a + b]`).
Bom, fiz o teste comparando os algoritmos com e sem desestruturação, e também com e sem `BigInt`: ```javascript var Benchmark = require('benchmark'); var suite = new Benchmark.Suite; suite .add('desestruturação', function () { const n = 1000; let a = 0, b = 1; for (let i = 0; i < n; i++) { [a, b] = [b, a + b]; } }) .add('sem desestruturação', function () { const n = 1000; let a = 0, b = 1; for (let i = 0; i < n; i++) { let tmp = a; a = b; b += tmp; } }) .add('desestruturação BigInt', function () { const n = 1000000n; let a = 0n, b = 1n; for (let i = 0n; i < n; i++) { [a, b] = [b, a + b]; } }) .add('sem desestruturação BigInt', function () { const n = 1000000n; let a = 0n, b = 1n; for (let i = 0n; i < n; i++) { let tmp = a; a = b; b += tmp; } }) .on('complete', function () { console.log('Fastest is ' + this.filter('fastest').map('name')); }) .on('cycle', function (event) { console.log(String(event.target)); }) .run({ 'async': true }); ``` Testei só no Linux, porque no Mac já vimos que `BigInt` fica bem mais lento usando o Bun. Resultados (em operações por segundo, ou seja, **quanto mais, melhor**): | Teste | Node | Bun | |------------------------------|-----------|-----------| | desestruturação | 236.387 | 3.920.325 | | sem desestruturação | 1.085.075 | 3.897.458 | | desestruturação `BigInt` | 0,11 | 0,11 | | sem desestruturação `BigInt` | 0,12 | 0,11 | Ou seja, parece que a implementação da desestruturação no Bun é mais rápida que no Node. Basta ver que sem desestruturação e sem `BigInt`, o Node pulou de 236 mil para 1 milhão de operações por segundo (e mesmo assim não chegou perto do Bun). Mas quando `BigInt` é usado, a diferença já não foi tão grande. Imagino que neste caso o *overhead* dos cálculos parece ter um impacto maior que a desestruturação.
Intrigante o resultado, o Bun é feito com o motor WebPack, que roda no Safari, ao invés do V8. Teoricamente o Bun deveria ir melhor no hardware da Apple, já que utiliza um recurso que a própria Apple utiliza e provavelmente otimiza por padrão. Outro adendo importante é que o Bun recomenda que esses tipos de teste em apps cli sejam feitos com o [hyperfine](https://github.com/sharkdp/hyperfine)
Vou compartilhar o resultado do benchmark com o M1 Pro: ``` ~/projects/bun-benchmark  node index.js test x 0.17 ops/sec ±2.08% (5 runs sampled) ~/projects/bun-benchmark  bun index.js test x 0.07 ops/sec ±1.74% (5 runs sampled) ``` Executei o teste algumas vezes, e em todas elas, o node foi em média 2x mais rápido. De fato, parece que o node é mais rápido no M1/M1 Pro (e ainda melhor no M1 Max), porém mais lento (ou praticamente igual) no Intel.
isso é muito louco porque é o oposto que eu imaginaria, pelo fato do bun usar runtime com engine do "safari"
> Eu vou postar um video amanhã sobre e vou colocar o link do TabNews nele Opa, que bom que avisou sobre o vídeo dessa vez! hahaha Pra aguentar o volume de acessos vindos pelo seu vídeo, já vou trocar aqui o Node pelo Bun no TabNews... 😜 Não, péra! Vendo os comparativos, é melhor adicionar uns pentes de memória e trocar o HD por SSD pra dar um gás no servidor 🤣🚀 Falando sério agora... É muito massa que sempre vem um pico de acessos e de novos cadastros quando você fala do TabNews nos seus vídeos. 💪🚀

Fiz alguns testes também sem bibliotecas em uma máquina linux com fedora 38 (dell Intel® Core™ i5-8350U × 8)

1. Teste de Operações com BigInt (Números de Fibonacci):

console.time("Execution Time");
const n = 1000000;
let a = BigInt(0), b = BigInt(1);
for (let i = BigInt(0); i < n; i++) {
  [a, b] = [b, a + b];
}
console.timeEnd("Execution Time");

Resultados: Node.js: 13.770s Bun: 32.01s

2. Teste de Operações de I/O:

console.time("Execution Time");
const n = 1000000;
for (let i = BigInt(0); i < n; i++) {
    console.log("Item: " + i);
}
console.timeEnd("Execution Time");

Resultados:

Node.js: 17.787s Bun: 5.27s

3. Teste de Alocação de Memória:

console.time("Memory Allocation Execution Time");
const n = 1000;
for (let i = 0; i < n; i++) {
    let arr = new Array(100000).fill(0);
}
console.timeEnd("Memory Allocation Execution Time");

Resultados:

Node.js: 2.162s Bun: 1.78s

4. Teste de Manipulação de Strings:

console.time("String Manipulation Execution Time");
let result = "";
const baseString = "abcdefghijklmnopqrstuvwxyz";
const n = 100000;
for (let i = 0; i < n; i++) {
    result += baseString;
}
console.timeEnd("String Manipulation Execution Time");

Resultados:

Node.js: 9.487ms Bun: 4.12ms

5. Teste de Manipulação de Arrays:

console.time("Array Operations Execution Time");
const n = 100000;
let arr = [];
for (let i = 0; i < n; i++) {
    arr.push(i);
}
for (let i = 0; i < n; i++) {
    let item = arr[i];
}
for (let i = 0; i < n; i++) {
    arr.pop();
}
console.timeEnd("Array Operations Execution Time");

Resultados:

Node.js: Média de 8.347ms Bun: Média de 7.91ms

6. Teste de Operações Matemáticas Básicas:

console.time("Basic Math Operations Execution Time");
let result = 0;
const n = 1000000;
for (let i = 1; i <= n; i++) {
    result += i;
    result -= i;
    result *= i;
    result /= i;
}
console.timeEnd("Basic Math Operations Execution Time");

Resultados:

Node.js: Média de 11.115ms Bun: Média de 15.423ms

Parece que o bun se destaca em operações de I/O, agora o Node.js tem uma vantagem em operações aritméticas, especialmente com BigInt

Só de curiosidade, refiz esses testes usando o [Benchmark.js](). Só adicionei mais um caso, de Fibonacci sem `BigInt`, pois vi que deu diferença. Segue o código: ```javascript var Benchmark = require('benchmark'); var suite = new Benchmark.Suite; suite .add('Fibonacci BigInt', function () { const n = 1000000n; let a = BigInt(0), b = BigInt(1); for (let i = BigInt(0); i < n; i++) { [a, b] = [b, a + b]; } }) .add('Fibonacci sem BigInt', function () { // tive que diminuir n para não estourar o valor máximo de Number const n = 1000; let a = 0, b = 1; for (let i = 0; i < n; i++) { [a, b] = [b, a + b]; } }) .add('console.log no loop', function () { const n = 1000000; for (let i = BigInt(0); i < n; i++) { console.log("Item: " + i); } }) .add('Alocação de memória', function () { const n = 1000; for (let i = 0; i < n; i++) { let arr = new Array(100000).fill(0); } }) .add('Manipulação de strings', function () { let result = ""; const baseString = "abcdefghijklmnopqrstuvwxyz"; const n = 100000; for (let i = 0; i < n; i++) { result += baseString; } }) .add('Manipulação de arrays', function () { const n = 100000; let arr = []; for (let i = 0; i < n; i++) { arr.push(i); } for (let i = 0; i < n; i++) { let item = arr[i]; } for (let i = 0; i < n; i++) { arr.pop(); } }) .add('Operações matemáticas', function () { let result = 0; const n = 1000000; for (let i = 1; i <= n; i++) { result += i; result -= i; result *= i; result /= i; } }) .on('cycle', function (event) { console.log(String(event.target)); }) .run({ 'async': true }); ``` Os resultados estão abaixo (os valores são de operações por segundo, ou seja, **quanto mais, melhor**). Linux (Ubuntu 22.04.3, processador 11th Gen Intel® Core™ i5-1145G7 2.60GHz, 8 núcleos): | Teste | Node | Bun | |:-----------------------|:-------|:---------------------------------| | Fibonacci BigInt | 0,07 | 0,07 | | Fibonacci sem BigInt | 224154 | 3507428 (sim, mais de 3 milhões) | | console.log no loop | 0,14 | 0,31 | | Alocação de memória | 2,51 | 2,54 | | Manipulação de strings | 2260 | 1024 | | Manipulação de arrays | 649 | 1882 | | Operações matemáticas | 198 | 222 | Mac (2012, processador 2.6 GHz Quad-Core Intel Core i7): | Teste | Node | Bun | |:-----------------------|:-------|:--------| | Fibonacci BigInt | 0,12 | 0,03 | | Fibonacci sem BigInt | 136482 | 1547414 | | console.log no loop | 0,08 | 0,18 | | Alocação de memória | 1,49 | 2,7 | | Manipulação de strings | 1483 | 1033 | | Manipulação de arrays | 453 | 1374 | | Operações matemáticas | 122 | 118 | Ou seja, o Node em geral se sai pior no `console.log` dentro de um loop (pelos motivos já citados em vários comentários). O cálculo com `BigInt` faz muita diferença no Mac (o Bun se sai pior), mas no Linux dá empate técnico. Em compensação, sem `BigInt` o Bun ganha por uma diferença enorme. E nas outras operações ora um, ora outro se sai melhor, com variados graus de vantagem.
wow! muito massa quebrar em 6 tipos de op. ficou muito bom ver a diferença! **IMPORTANTE:** `console.log` nem sempre vai ser I/O, no NodeJS de fato ele vai virar um `process.stdout.write()` e olhando pro [codigo fonte](https://github.com/oven-sh/bun/blob/d26addeca147e076d7a5e2c7fe14febdd658393f/src/bun.js/bindings/exports.zig#L2843) do bun: > but it's important that console.log() is fast. No bun o `console.log` vai virar um **synchronous printf** vs. no NodeJS que se vai ser um asynchronous com stdout

Talvez a perda de performance seja por conta do arquivo não ser em typescript, temos esse trecho na página do Bun:

Bun is fast, starting up to 4x faster than Node.js. This difference is only magnified when running a TypeScript file, which requires transpilation before it can be run by Node.js.

Me parece um artifício para que as pessoas tenham algum hype de o Bun ser um Node Killer. Mas lendo o que eles mesmo escreveram da pra perceber que o foco maior não é na performance em si, mas em reduzir a complexidade de projetos que antes precisavam de 5 libs diferentes para rodar um simples arquivo.

Tenho curiosidade também de ver comparações em outros contextos, como operações de IO, com os módulos nativos do Bun e também com algoritmos recursivos. Vou tirar um tempo no próximo fim de semana para fazer algo do tipo.

O trecho citado fala de tempo de inicialização, então para comparar corretamente teria que fazer um benchmark que chama várias vezes o node e o bun, ao invés de repetir varias vezes o mesmo código.
Isso acaba reforçando o que eu disse sobre o hype. O que tem sido divulgado é que o Bun é mais rápido que o Node no geral, mas isso não está em lugar nenhum. Ele é mais rápido em determinadas situações, como essa do typescript (comparando com ts-node e tsx), na leitura de arquivos, testes e etc. Se comparar nessas situações e nos casos mostrados provavelmente vai ser mais rápido mesmo, mas não quer dizer que será em todas as situações. Eu acredito que o Bun vai conseguir crescer, difere te do Deno. Eu penso nisso pois o Bun abstrai e melhora muita coisa que para utilizarmos no Node precisamos de inúmeras libs. A experiência de desenvolvimento com o Bun é no geral melhor do que com o Node e é esse o forte do Bun. Mas tudo isso seria inútil se não houvesse compatibilidade com o próprio Node, um dos motivos que acabou com o destaque do Deno. O fato de poder utilizar o Bun como uma alternativa a npm, yarn e pnpm é uma das coisas que irá fazer ele ter destaque, mas o que mais me impressionou foi a suíte de testes nativa. O Bun matou a pau com essa suíte de testes e com o tempo só irá melhorar. Basicamente para migrar um projeto atual com Jest para Bun não é preciso fazer praticamente nada. No caso só seria necessário fazer as importações, mas se você importa as funcionalidades do Jest de `@jest/globals` ou utiliza Vitest, então então não precisa fazer nada e pode até mesmo desinstalar essas dependências. O Bun também já vem com dotenv e bcrypt nativos, o que retira a necessidade de duas libs no seu projeto. Se for para utilizar websockets, o Bun também tem um objeto nativo para isso, diferente do Node. Só no que eu falei aqui já retiraria de 3 a 5 dependências da maioria dos projetos que utilizam o Node, isso que nem falei sobre não serem necessários bundlers no projeto, que já reduziria quase que pela metade o tamanho da node_modules. Com tudo isso, o Bun não precisa necessariamente ser mais rápido que o Node, porque ele já é melhor que o Node. Eu digo isso não por achar que ele vai realmente matar o Node, mas porque ele tem duas vantagem principais: não precisa se preocupar com a compatibilidade com versões anteriores e não é o primeiro a fazer o que faz. Muitas melhorias no Node não são possíveis de implementar rapidamente por conta de compatibilidade e outras porque ele apenas foi o primeiro a implementar (inclusive algumas coisas antes da própria linguagem ter algum tipo de padrão). Isso faz com que o Bun possa implementar muita coisa rapidamente, como já vem fazendo, principalmente porque tem todo o histórico do node para saber o que é melhor ou pior de se fazer. Mas agora, falando de estabilidade, o Node está anos luz a frente do Bun, afinal são 14 anos no mercado.
Concordo com tudo o que você disse, e adiciono de que nada impede que com o tempo o node também implemente e faça algumas melhorias no sentido de melhora da experiencia de desenvolvimento, diminuindo a "vantagem" do bun. Na versão 20.6 se não me engano, já existe o suporte nativo a arquivos .env, tirando a necessidade de pacote adicional, com o tempo acredito que o mesmo pode acontecer com outras libs e funcionalidades. Talvez o problema seja a retrocompatibilidade, mas isso toda e qualquer técnologia está sujeita, vai do desenvolvedor refatorar e atualizar.
Foi a mesma coisa quando o yarn chegou com inúmeras vantagens, e o npm incluiu todas elas posteriormente, de modo que hoje não se tem tanta vantagem em usar yarn. Daí surge o pnpm, com outras vantagens, e o npm corre atrás. É a vantagem de ter uma concorrência. A tecnologia sempre vai evoluir para não ficar para trás.
Adicionando misterio a essa desafio: Se fizer o loop a seguir sem a matematica do Fibonacci o Bun desempenha melhor que o NodeJS. ```javascript const n = 1000000 for (let i = BigInt(0); i < n; i++) { console.log("Item: " + i); } ```

Depende de que forma mais rapido, o problema do node não é só em velocidade de execução, o tooling é uma merda, transpilar e configurar um projeto ts é chato. Com bun tu tem praticamente tudo isso out of the box. Sendo que o tooling é inclusive mais rapido que o pnpm. E a loucura de esmodule e common js, tudo isso é horrível no node.

Eu fiz alguns benchmarcks hoje nessa mesma pegada, mas ao invés de utilizar só Fibonacci eu fiz juntamente com outros algoritmos e também utilizando os respectivos recursivos nos casos onde se aplicam.

Na minha máquina que é Intel os resultados realmente foram muito discrepantes, o Bun deu de lavada no Node, o que me faz acreditar que nesses casos relatados tem algo a ver com os chips M1 mesmo.

Eu fiz um repositório com todas as implementações e o resultados dos testes. Se quiserem ver é esse aqui: https://github.com/Gabriel-Tapes/bun-vs-node.

Os testes foram até que bem simples, mas vou dar uma resumida aqui:

Eu utilizei o hyperfine para fazer os benchmarcks em uma configuração de 50 testes warmup antes de rodar o benchmarck real, que rodava os arquivos 1000 vezes.

Os meus resultados estão aqui: https://github.com/Gabriel-Tapes/bun-vs-node/blob/main/results.md

Executei uma série de benchmarks na minha máquina com as seguintes especificações:

  • WSL 2 Ubuntu 22.04.2 LTS
  • AMD Ryzen 7 5700x 4.6Ghz, 8 núcleos
  • RAM 16gb 3200Mhz CL18

Utilizei uma suíte de testes fornecida pelo @kht, que incluiu os seguintes casos de teste:

var Benchmark = require('benchmark');
var suite = new Benchmark.Suite;

suite
.add('desestruturação', function () {
    const n = 1000;
    let a = 0, b = 1;
    for (let i = 0; i < n; i++) {
      [a, b] = [b, a + b];
    }
})
.add('sem desestruturação', function () {
    const n = 1000;
    let a = 0, b = 1;
    for (let i = 0; i < n; i++) {
        let tmp = a;
        a = b;
        b += tmp;
    }
})
.add('desestruturação BigInt', function () {
    const n = 1000000n;
    let a = 0n, b = 1n;
    for (let i = 0n; i < n; i++) {
      [a, b] = [b, a + b];
    }
})
.add('sem desestruturação BigInt', function () {
    const n = 1000000n;
    let a = 0n, b = 1n;
    for (let i = 0n; i < n; i++) {
        let tmp = a;
        a = b;
        b += tmp;
    }
})
.on('complete', function () {
    console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.on('cycle', function (event) {
    console.log(String(event.target));
})
.run({
    'async': true
});

Os resultados obtidos foram os seguintes:

Teste Node Bun
desestruturação 1.350.540 884.238
sem desestruturação 1.392.095 1.662.632
desestruturação BigInt 0.16 0.10
sem desestruturação BigInt 0.16 0.10

Só queria agregar aqui tenho um notebook que está usando o POP_OS 22.04 e rodei o bun com o zsh. esse mesmo Fibonacci demorou em média 14s enquanto o node demorou 12s.

tenho um i7 de 8ª geração. Durante a execução estava com o Google Chrome, Egde, VS Code aberto.

Alo pessoal. A galera ja comentou sobre diversos fatores que podem influenciar aqui... so queria compartilhar os resultados de uma maquina mais "humilde" (que nao é humilde pq no fim eh um mac mas vcs entenderam...)

Configuracao

MacBook Air 2020 1,6 GHz Dual-Core Intel Core i5, no macOS Ventura 13.4 (22F66) Node --v 18.17.1 Bun -version 1.0.2

Teste

console.time("execution time")

function fib(n) {
  let a = BigInt(0), b = BigInt(1);
  for (let i = BigInt(0); i < n; i++) {
    [a, b] = [b, a + b]
    console.log("a:", a, "b:",b)
  }
  return a.toString()
}

for(let i = 100; i > 0; i--){
  let result = fib(10000)
  console.log(result)
}

console.timeEnd("execution time")

Resultado

Runtime Tempo total(m:ss.mmm) Média(ss.mmm)
Node 7:06.114 04.261
Bun 9:42.06 05.821

Ao que parece ser realmente é a forma como a compilação do bun em arm64 está funcionando, tem alguns issues acerca da arquitetura, erros que não ocorrem na compilação realizada para amd/intel. Pode ser que seja alguma implementação para arm64 que faz com que seja mais rápido.

Um dos principais core do projeto (Jarred-Sumner), fez a seguinte fala: If you run into performance issues with real code in your application feel free to file an issue

This looks more like a microbenchmark. JS engines are really good at optimizing microbenchmarks with statically known data. It probably isn't meaningful in applications.

logo:

Se você tiver problemas de desempenho com código real em seu aplicativo, sinta-se à vontade para registrar um problema

Isso se parece mais com um microbenchmark. Os mecanismos JS são realmente bons para otimizar microbenchmarks com dados estaticamente conhecidos. Provavelmente não é significativo em aplicativos.

https://github.com/oven-sh/bun/issues/3358

Sobre o código:

`var appendDelay = 1; var chartLen = 10; var delaysSoFar = 0; var arr = [0];

function update(x) { if (arr.length < chartLen) { if (delaysSoFar == appendDelay) { arr.push(x); delaysSoFar = 0; } else { arr[arr.length - 1] = x; } delaysSoFar += 1; }

if (arr.length == chartLen) { for (var i = 0; i < 10; i += 2) { arr[i / 2] = arr[i]; } arr.length = 5;

appendDelay *= 2; arr.push(x); } }

const now = performance.now(); for (var i = 0; i < 1000000000; i++) { update(i); } console.log(performance.now() - now);

console.log(arr);`

obs. ~~ Outro detalhe que eu acredito fazer parte do processo é que mesmo o webpack sendo apple, ela roda em outras máquinas que utilizam o webkit como o gnome epiphany (browser do gnome uma DE linux). Então não acredito que seja isso que possa fazer alguma diferença. ~~ E um ultimo detalhe é Lucas sempre vejo seus videos, bom trabalho.

Que balde de água fria, hein. Está o maior hype que o bun é mais rápido porque foi feito em Rust. Um monte de gente repetindo que bun é mais rápido, porém a realidade é que praticamente estão empatados. Supostamente pelo menos bun é mais seguro, mas não duvido alguém fazer testes reais e descobrir falhas. Nota: Estou repetindo feito papagaio que o Rust é seguro, por ouvir que ele é seguro, mas não manjo de segurança e claramente nunca testei a segurança de Rust.

Mas o Bun é escrito em Zig, não tem nada de Rust no código dele, [veja](https://github.com/oven-sh/bun). Quem foi feito em Rust é o [Deno](https://github.com/denoland/deno). De qualquer forma, pelos testes básicos que fiz, e por vários benchmarks que vi por aí, em alguns casos o Bun foi mais rápido sim. Mas sempre tem quem compra o marketing cegamente e entende "*então é sempre mais rápido*". Não é. Até onde vi, depende do caso, tem vezes que é melhor, em outras é pior e em outras tanto faz. Como eu disse no [outro comentário](/kht/132ba7e8-eee5-44eb-90b9-c4fe78316332), o ideal é que cada um testasse para ver se no seu caso específico faz diferença, e então decidir se vale a pena. Infelizmente, parece que muitas pessoas preferem acreditar na solução mágica que sempre vai ser melhor pra tudo. E pra essas, qualquer coisa será um balde de água fria, pois tal solução não existe.
Então piorou, há mais mentiras ainda, eu sempre ouvi falar que usaram Rust.
Sempre ouviu de quem? Na [documentação oficial](https://bun.sh/docs), logo no segundo parágrafo, tá bem claro que foi escrito em Zig. Se alguém mentiu, não foram os criadores do Bun, provavelmente foi alguém que sequer leu a documentação... ¯\\\_(ツ)\_/¯
Está pasando verganha dizendo algo que você claramente não sabe, está apenas repetendo igual um papagaio o que ouviu por ai, se eu fosse você já teria apagado esse post correndo!