Sim, eu disse isso! Os retornos de chamada são mais rápidos quando executados em paralelo em async/await em paralelo. E apenas mais rápido quando executamos retornos de chamada sequenciais. 4,8x 1,9x Modifiquei um pouco este artigo depois de receber alguns comentários úteis e gentis sobre meu teste duvidoso. 😂🙏 Obrigada por e por reservar um tempo para orientar os benchmarks na direção certa. Minha primeira gafe foi que eu não estava realmente esperando a execução do código terminar, o que distorceu loucamente os resultados. A segunda foi que eu estava comparando tempos de execução paralelos e sequenciais, o que torna os benchmarks inúteis. Ricardo Lopes Ryan Poe Portanto, esta é a segunda rodada, que aborda meus erros iniciais. Anteriormente eu disse: Originalmente, escrevi, que o NodeJS é 34,7x mais rápido se voltarmos aos retornos de chamada! 🤣 Errado. Não tão impressionante quanto meus benchmarks ruins anteriores (e veja os comentários para contextualizar), mas ainda assim uma diferença considerável. Então, o que exatamente eu testei? Comparei retornos de chamada com promessas e async/await ao ler um arquivo, 10.000 vezes. E talvez seja um teste bobo, mas eu queria saber qual é o mais rápido na E/S. Então finalmente comparei os retornos de chamada no Node.js to Go! Agora, adivinhe quem ganhou? Eu não serei mau. . Golang! TLDR Menor é melhor. Os resultados estão em . ms Agora, verdadeiros benchmarkers? Vá com calma comigo neste caso. Mas por favor, deixe seus comentários para me tornar uma pessoa melhor. Todo mundo fica dizendo que o Node.js é lento! E isso me incomoda. Porque o que significa lento? Tal como acontece com todos os benchmarks, o meu é contextual. Comecei a ler sobre , só para começar a entender . Ciclo de eventos como funciona Mas a principal coisa que entendi é que o Node.js passa tarefas de E/S para uma fila que fica fora do thread executável principal do Node.js. Esta fila é executada em . Vários threads poderiam potencialmente lidar com essas operações de E/S. E é aí que o Node.js pode brilhar, lidando com E/S. C puro As promessas, no entanto, são tratadas no único thread executável principal. E async/await, está bem, promete, mas agora com bloqueio adicionado. Então, os retornos de chamada são mais rápidos do que as promessas? Vamos colocar isso à prova. Primeiramente. Minha ! Complementos de trabalhar com . É importante observar com quais recursos estamos trabalhando. Muita memória e CPU. máquina Carma MacBook Pro (14-inch, 2021) Chip Apple M1 Pro Memory 32 GB Cores 10 NodeJS v20.8.1 Portanto, temos um arquivo com uma mensagem , . text.txt original Hello, world echo "Hello, world" > text.txt E leremos este arquivo de texto usando Node.js nativo, o que significa zero dependências de módulo de nó porque não queremos diminuir a velocidade com os objetos mais pesados do universo. Retornos de chamada Retornos de chamada paralelos Primeiro, vamos começar com retornos de chamada . Estou interessado em saber a rapidez com que o mesmo arquivo pode ser lido o mais rápido possível, tudo de uma vez. E o que é mais rápido que o paralelo? paralelos // > file-callback-parallel.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs"; test('reading file 10,000 times with callback parallel', (t, done) => { let count = 0; for (let i = 0; i < 10000; i++) { fs.readFile("./text.txt", { encoding: 'utf-8'}, (err, data) => { assert.strictEqual(data, "Hello, world"); count++ if (count === 10000) { done() } }) } }); Retornos de chamada sequenciais Em segundo lugar, temos retornos de chamada novamente, mas sequenciais (ou melhor, bloqueadores). Estou interessado em saber a rapidez com que o mesmo arquivo pode ser lido sequencialmente. Não tendo feito retornos de chamada há muito tempo, foi divertido tentar novamente. Embora não pareça bonito. // > file-callback-blocking.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs"; let read = (i, callback) => { fs.readFile("./text.txt", { encoding: 'utf-8'}, (err, data) => { assert.strictEqual(data, "Hello, world"); i += 1 if (i === 10000) { return callback() } read(i, callback) }) } test('reading file 10,000 times with callback blocking', (t, done) => { read(0, done) }); Assíncrono/Aguardar Então temos async/await. Minha maneira favorita de trabalhar com Nodejs. Paralelo assíncrono/aguardado É o mais paralelo possível com async/await. Carrego todas as operações em um array e aguardo todas elas usando . readFile Promise.all // > file-async-parallel.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs/promises"; test('reading file 10,000 times with async parallel', async (t) => { let allFiles = [] for (let i = 0; i < 10000; i++) { allFiles.push(fs.readFile("./text.txt", { encoding: 'utf-8'})) } return await Promise.all(allFiles) .then(allFiles => { return allFiles.forEach((data) => { assert.strictEqual(data, "Hello, world"); }) }) }); Sequencial Assíncrono/Aguarda Este foi o mais fácil e conciso de escrever. // > file-async-blocking.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs/promises"; test('reading file 10,000 times with async blocking', async (t) => { for (let i = 0; i < 10000; i++) { let data = await fs.readFile("./text.txt", { encoding: 'utf-8'}) assert.strictEqual(data, "Hello, world"); } }); Promessas Finalmente, temos promessas sem async/await. Há muito tempo parei de usá-los em favor de , mas estava interessado em saber se eles tinham desempenho ou não. async/await Promessas paralelas // > file-promise-parallel.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs/promises"; test('reading file 10,000 times with promise parallel', (t, done) => { let allFiles = [] for (let i = 0; i < 10000; i++) { allFiles.push(fs.readFile("./text.txt", { encoding: 'utf-8'})) } Promise.all(allFiles) .then(allFiles => { for (let i = 0; i < 10000; i++) { assert.strictEqual(allFiles[i], "Hello, world"); } done() }) }); Promessas sequenciais. Novamente, queremos aguardar a execução de todas as operações . readFile // > file-promise-blocking.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs/promises"; test('reading file 10,000 times with promises blocking', (t, done) => { let count = 0; for (let i = 0; i < 10000; i++) { let data = fs.readFile("./text.txt", { encoding: 'utf-8'}) .then(data => { assert.strictEqual(data, "Hello, world") count++ if (count === 10000) { done() } }) } }); E pronto! Resultados 🎉! Até corri algumas vezes para obter uma leitura melhor. Eu executei cada teste fazendo: node --test <file>.mjs Também é 4,7x mais rápido do que com promessas em paralelo! Ler um arquivo 10.000 vezes com retornos de chamada é 5,8x mais rápido do que com async/await em paralelo! Portanto, no terreno do Node.js, os retornos de chamada melhor desempenho! têm Agora o Go é mais rápido que o Node.js? Bem, eu não escrevo em Go, então este pode ser um código realmente terrível porque pedi ao ChatGPT para me ajudar e, ainda assim, bastante decente. parece Ei, ei. Vamos. Nosso código Golang. package main import ( "fmt" "io/ioutil" "time" ) func main() { startTime := time.Now() for i := 0; i < 10000; i++ { data, err := ioutil.ReadFile("./text.txt") if err != nil { fmt.Printf("Error reading file: %v\n", err) return } if string(data) != "Hello, world" { fmt.Println("File content mismatch: got", string(data), ", want Hello, world") return } } duration := time.Since(startTime) fmt.Printf("Test execution time: %v\n", duration) } E nós executamos assim: go run main.go E os resultados? Test execution time: 58.877125ms 🤯 Go é 4,9x mais rápido que Node.js usando retornos de chamada sequenciais. O Node.js só chega perto da execução paralela. Node.js Async/await é 9,2x mais lento que Go. Então sim. Node.js é mais lento. Ainda assim, 10.000 arquivos em menos de 300 ms não são motivo de zombaria. Mas fiquei emocionado com a rapidez do Go! Agora apenas uma observação lateral. Tenho benchmarks ruins? Eu realmente tive benchmarks terríveis. Obrigado novamente a Ricardo e Ryan. Sim eu fiz. Espero que agora eles estejam melhores. Mas você pode perguntar: quem realmente vai ler o mesmo arquivo repetidamente? Mas para um teste relativo entre as coisas, espero que seja uma comparação útil. Também não sei quantos threads o Node.js está usando. Não sei como os núcleos da minha CPU afetam o desempenho do Go vs Node.js. Eu poderia simplesmente alugar uma máquina AWS com um núcleo e comparar. É porque estou no Mac M1? Qual seria o desempenho do Node.js em um Linux ou... Windows? 😱 E tem a praticidade de, sim, ler um arquivo é uma coisa, mas em algum momento você tem que esperar de qualquer maneira que o arquivo seja lido para fazer algo com os dados contidos no arquivo. Portanto, a velocidade no thread principal ainda é muito importante. Agora, você realmente deseja usar retornos de chamada? Quero dizer, você realmente quer? Não sei. Definitivamente não quero dizer a ninguém o que fazer. Mas gosto da sintaxe limpa de async/awaits. Eles parecem melhores. Eles leem melhor. Eu sei que é subjetivo aqui, mas lembro-me do retorno de chamada e fiquei grato quando as promessas surgiram. Tornou o Javascript suportável. Agora, Golang é claramente mais rápido que o Node.js no seu nível ideal, com retornos de chamada e com assíncrono/espera, em 9,2x! Portanto, se quisermos boa legibilidade e desempenho, Golang é o vencedor. Embora eu adoraria saber como Golang se parece por baixo do capô. Qualquer um. Isso foi divertido. Foi mais um exercício para me ajudar a entender como os retornos de chamada e E/S funcionam no Event Loop. Então, para sair O Node.js é lento? Ou estamos apenas usando o Node.js no modo lento? Provavelmente onde o desempenho é importante, vale a pena saltar para Golang. Certamente procurarei mais usar Golang no futuro. Também aparece . aqui