はい、言いました!
コールバックは、async/await を並行して実行すると4.8 倍高速になります。シーケンシャル コールバックを実行すると、わずか1.9 倍高速になります。
私の危険なテストに関して有益で親切なコメントをいくつかいただいたので、この記事を多少修正しました。 😂🙏
ありがとうございました
リカルド・ロペス そしてライアン・ポー ベンチマークを正しい方向に導くために時間を割いていただきました。私の最初の失敗は、コードの実行が完了するのを実際に待っていなかったことで、結果がひどく歪んでしまいました。 2 つ目は、並列ランタイムとシーケンシャルランタイムを比較していたため、ベンチマークの価値がなくなってしまったことです。
したがって、これは私の最初のエラーに対処するラウンド 2 です。以前、私はこう言いました。
当初、コールバックに戻れば NodeJS は 34.7 倍高速になると書きました。 🤣違います。
以前の悪いベンチマークほど印象的ではありませんが (文脈についてはコメントを参照してください)、それでもかなりの差があります。
ファイル読み取り時のコールバックと Promise および async/await を 10,000 回比較しました。それは愚かなテストかもしれませんが、どちらの I/O が速いのかを知りたかったのです。
そして最後に、Node.js のコールバックと Go を比較してみました。
さて、誰が勝ったと思いますか?
意地悪はしませんよ。 TLDR 。ゴラン!
低いほど良いです。結果は
ms
で表示されます。
さて、本当のベンチマークは何でしょうか?これについては気楽に言ってください。しかし、私をより良い人間にするために、ぜひコメントを残してください。
そしてそれは私を悩ませます。
だって、遅いってどういう意味ですか?すべてのベンチマークと同様に、私のベンチマークも状況に応じたものです。
について読み始めました
しかし、私が理解した主な点は、Node.js はメインの Node.js 実行可能スレッドの外側にあるキューに I/O タスクを渡すということです。このキューは次のように実行されます
ただし、Promise はメインの単一の実行可能スレッドで処理されます。そして、async/await は確かに約束されていますが、ブロックが追加されました。
テストしてみましょう。
最初に。私のマシン!協力することの補足
MacBook Pro (14-inch, 2021) Chip Apple M1 Pro Memory 32 GB Cores 10 NodeJS v20.8.1
したがって、元のメッセージHello, world
を含むtext.txt
ファイルが存在します。
echo "Hello, world" > text.txt
そして、ネイティブ Node.js を使用してこのテキスト ファイルを読み取ります。つまり、宇宙で最も重いオブジェクトで速度を低下させたくないため、ノード モジュールの依存関係はゼロになります。
まず、並列コールバックから始めましょう。同じファイルをできるだけ早く、一度にどれだけ速く読み取ることができるかに興味があります。並列より速いものは何でしょうか?
// > 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() } }) } });
2 番目に、コールバックが再びありますが、シーケンシャル (またはブロック) です。同じファイルを連続してどれくらい速く読み取ることができるかに興味があります。コールバックを呼び出すコールバックを長年やっていなかったので、もう一度試すのは楽しかったです。とはいえ、見た目は美しくありません。
// > 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) });
次に、async/await があります。私のお気に入りの Nodejs の操作方法。
async/await で可能な限り並列です。すべての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"); }) }) });
これが最も簡単で簡潔に書くことができました。
// > 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"); } });
最後に、async/await を使用しない Promise があります。私はasync/await
を支持して長い間それらを使用するのをやめていましたが、それらのパフォーマンスが高いかどうかに興味がありました。
// > 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() }) });
繰り返しますが、すべての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() } }) } });
そして出来上がり!結果🎉!さらに読みやすくするために数回実行しました。
次のようにして各テストを実行しました。
node --test <file>.mjs
コールバックを使用してファイルを 10,000 回読み取ると、async/await を並行して使用するよりも 5.8 倍以上高速になります。また、Promise を並列に実行した場合よりも 4.7 倍高速です。
したがって、Node.js の世界では、コールバックのパフォーマンスが向上します。
まあ、私は Go で書いていないので、ChatGPT に助けを求めたので、これは本当にひどいコードかもしれませんが、それでもかなりまともに見えます。
おいおい。さあ行こう。私たちの 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) }
そして、次のように実行します。
go run main.go
そして結果は?
Test execution time: 58.877125ms
🤯 Go は、シーケンシャル コールバックを使用すると Node.js より 4.9 倍高速です。 Node.js は並列実行でのみそれに近づきます。
Node.js Async/await は Go より 9.2 倍遅いです。
あ、はい。 Node.js は遅いです。それでも、300 ミリ秒未満で 10,000 個のファイルを処理できるというのは、嘲笑されるべきものではありません。でも、Goさんのスピード感には頭が下がる思いです!
本当にひどいベンチマークでした。リカルドとライアンに改めて感謝します。
はい、そうでした。今は良くなっているといいのですが。
しかし、実際に同じファイルを何度も読み取るのは誰なのでしょうか?と疑問に思うかもしれません。しかし、物事間の相対的なテストとしては、これが役立つ比較になることを願っています。
Node.js が使用しているスレッドの数もわかりません。
CPU コアが Go と Node.js のパフォーマンスにどのような影響を与えるかわかりません。
1 コアの AWS マシンを借りて比較することもできます。
Mac M1を使っているからでしょうか?
Linux や Windows では Node.js はどのように動作しますか? 😱
そして、確かに、ファイルを読み取ることは別のことですが、ある時点で、ファイル内のデータに対して何かを行うには、ファイルが読み取られるまで待たなければならないという実用性もあります。したがって、メインスレッドの速度は依然として非常に重要です。
つまり、本当に本当にしたいのですか?
わからない。私は絶対に誰にも何をすべきか言いたくない。
しかし、私は async/awaits のきれいな構文が好きです。
見た目も良くなります。
彼らは読書が上手です。
ここでのベターが主観的であることは承知していますが、コールバック地獄を思い出し、約束が実現したときは感謝していました。これにより、JavaScript が耐えられるようになりました。
現在、Golang は、コールバックと async/await を使用すると、最適な状態で Node.js よりも明らかに 9.2 倍高速になります。したがって、優れた可読性とパフォーマンスが必要な場合は、Golang が勝者です。ただし、Golang が内部でどのように見えるかを知りたいと思っています。
誰でも。これは楽しかったです。これは、イベント ループでコールバックと I/O がどのように機能するかを理解するための演習のようなものでした。
Node.jsは遅いですか?それとも Node.js を低速モードで使用しているだけですか?
おそらく、パフォーマンスが重要な場合には、Golang を利用する価値があります。将来的には Golang の使用をさらに検討するつもりです。
ここにも登場します。