Ouais, je l'ai dit ! Les rappels sont plus rapides lorsqu'ils sont exécutés en parallèle sur async/wait en parallèle. Et seulement plus rapide lorsque nous exécutons des rappels séquentiels. 4,8 fois 1,9 fois J'ai quelque peu modifié cet article après avoir reçu des commentaires utiles et aimables sur mon test douteux. 😂🙏 Merci de et pour avoir pris le temps d’orienter les benchmarks dans la bonne direction. Mon premier faux pas était que je n'attendais pas réellement la fin de l'exécution du code, ce qui faussait follement les résultats. La seconde était que je comparais des exécutions parallèles à des exécutions séquentielles, ce qui rend les benchmarks sans valeur. Ricardo Lopés Ryan Poé C'est donc le deuxième tour qui corrige mes erreurs initiales. Auparavant, je disais : À l’origine, j’ai écrit que NodeJS est 34,7 fois plus rapide si l’on revient aux rappels ! 🤣 Faux. Pas aussi impressionnant que mes mauvais benchmarks précédents (et voir les commentaires pour le contexte), mais toujours une différence considérable. Alors qu'est-ce que j'ai testé exactement ? J'ai comparé les rappels aux promesses et à l'async/wait lors de la lecture d'un fichier, 10 000 fois. Et c'est peut-être un test idiot, mais je voulais savoir ce qui est plus rapide en E/S. Ensuite, j'ai finalement comparé les rappels dans Node.js avec Go ! Maintenant, devinez qui a gagné ? Je ne serai pas méchant. . Golang! TLDR Plus bas, c'est mieux. Les résultats sont en . ms Maintenant, de vrais benchmarkers ? Allez-y doucement avec moi sur celui-ci. Mais s'il vous plaît, laissez vos commentaires pour faire de moi une meilleure personne. Tout le monde n’arrête pas de dire que Node.js est lent ! Et ça me dérange. Car que signifie ralentir ? Comme pour tous les benchmarks, le mien est contextuel. J'ai commencé à lire sur le , juste pour commencer à comprendre . Boucle d'événement Comment ça fonctionne Mais la principale chose que j'ai comprise est que Node.js transmet les tâches d'E/S dans une file d'attente située en dehors du thread exécutable principal de Node.js. Cette file d'attente s'exécute sur . Un certain nombre de threads pourraient potentiellement gérer ces opérations d’E/S. Et c'est là que Node.js peut briller, en gérant les E/S. C pur Les promesses, cependant, sont gérées dans le thread exécutable principal unique. Et async/wait, c'est bien, promet mais maintenant avec le blocage ajouté. Alors les rappels sont-ils plus rapides que les promesses ? Mettons-le à l'épreuve. Tout d'abord. Ma ! Compléments du travail avec . Il est important de noter avec quelles ressources nous travaillons. Beaucoup de mémoire et de CPU. machine Kamma MacBook Pro (14-inch, 2021) Chip Apple M1 Pro Memory 32 GB Cores 10 NodeJS v20.8.1 Nous avons donc un fichier avec un message , . text.txt original Hello, world echo "Hello, world" > text.txt Et nous lirons ce fichier texte en utilisant Node.js natif, ce qui signifie zéro dépendance de module de nœud car nous ne voulons pas ralentir la vitesse avec les objets les plus lourds de l'univers. Rappels Rappels parallèles Commençons par les rappels . Je m'intéresse à la rapidité avec laquelle le même fichier peut être lu le plus rapidement possible, d'un seul coup. Et quoi de plus rapide que le parallèle ? parallèles // > 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() } }) } }); Rappels séquentiels Deuxièmement, nous avons à nouveau des rappels, mais séquentiels (ou plutôt bloquants). Je m'intéresse à la rapidité avec laquelle le même fichier peut être lu séquentiellement. N'ayant pas effectué de rappels depuis des lustres, c'était amusant de réessayer. Mais ça n'a pas l'air joli. // > 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) }); Asynchrone/Attente Ensuite, nous avons async/wait. Ma façon préférée de travailler avec Nodejs. Asynchrone/attente parallèle C'est aussi parallèle que possible avec async/await. Je charge toutes les opérations dans un tableau et je les attends toutes en utilisant . 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"); }) }) }); Asynchrone/attente séquentielle C’était le plus simple et le plus concis à écrire. // > 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"); } }); Promesses Enfin, nous avons des promesses sans async/wait. J'ai depuis longtemps arrêté de les utiliser au profit de mais je voulais savoir s'ils étaient performants ou non. async/await Des promesses parallèles // > 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() }) }); Des promesses séquentielles. Encore une fois, nous voulons attendre l'exécution de toutes les opérations . 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() } }) } }); Et voilà ! Résultats 🎉! Je l'ai même exécuté plusieurs fois pour obtenir une meilleure lecture. J'ai exécuté chaque test en faisant : node --test <file>.mjs C'est aussi 4,7x plus rapide qu'avec des promesses en parallèle ! Lire un fichier 10 000 fois avec des rappels est plus de 5,8 fois plus rapide qu'avec async/wait en parallèle ! Ainsi, au pays Node.js, les rappels plus performants ! sont Go est-il désormais plus rapide que Node.js ? Eh bien, je n'écris pas en Go, donc cela peut être un code vraiment terrible car j'ai demandé à ChatGPT de m'aider et pourtant, cela plutôt correct. semble Hé ho. Allons-y. Notre code 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) } Et nous l'exécutons comme ceci : go run main.go Et les résultats ? Test execution time: 58.877125ms 🤯 Go est 4,9 fois plus rapide que Node.js en utilisant des rappels séquentiels. Node.js ne se rapproche qu'avec une exécution parallèle. Node.js Async/await est 9,2 fois plus lent que Go. Donc oui. Node.js est plus lent. Pourtant, 10 000 fichiers en moins de 300 ms ne sont pas à dédaigner. Mais j'ai été touché par la rapidité de Go ! Maintenant, juste une remarque. Ai-je de mauvais repères ? J'avais vraiment de terribles benchmarks. Merci encore à Ricardo et Ryan. Oui je l'ai fait. J'espère que maintenant ils vont mieux. Mais vous vous demandez peut-être qui va réellement lire le même fichier, encore et encore ? Mais pour un test relatif entre les choses, j'espère que c'est une comparaison utile. Je ne sais pas non plus combien de threads Node.js utilise. Je ne sais pas comment mes cœurs de processeur affectent les performances de Go vs Node.js. Je pourrais simplement louer une machine AWS avec un seul cœur et comparer. Est-ce parce que je suis sur Mac M1 ? Comment Node.js fonctionnerait-il sur Linux ou... Windows ? 😱 Et il y a l'aspect pratique de, oui, lire un fichier est une chose, mais à un moment donné, vous devez de toute façon attendre que le fichier soit lu pour faire quelque chose avec les données qu'il contient. Ainsi, la vitesse sur le thread principal est toujours assez importante. Maintenant, voulez-vous vraiment utiliser les rappels ? Je veux dire, est-ce que tu le veux vraiment, vraiment ? Je ne sais pas. Je ne veux absolument dire à personne quoi faire. Mais j'aime la syntaxe claire de async/awaits. Ils ont l'air mieux. Ils lisent mieux. Je sais que mieux est subjectif ici, mais je me souviens de l'enfer des rappels, et j'étais reconnaissant lorsque les promesses ont vu le jour. Cela a rendu Javascript supportable. Désormais, Golang est clairement plus rapide que Node.js à son optimal, avec des rappels et avec async/await, de 9,2x ! Donc si nous voulons une bonne lisibilité et performances, Golang est le gagnant. Cependant, j'aimerais savoir à quoi ressemble Golang sous le capot. N'importe qui. C'était amusant. Il s'agissait plutôt d'un exercice pour m'aider à comprendre comment fonctionnent les rappels et les E/S dans la boucle d'événements. Alors pour me déconnecter Node.js est-il lent ? Ou utilisons-nous simplement Node.js en mode lent ? Probablement là où les performances comptent, Golang vaut le coup. J'envisagerai certainement davantage d'utiliser Golang à l'avenir. Apparaît également . ici