paint-brush
NodeJS: コールバックに戻すと 4.8 倍高速になります!by@gemmablack
19,696
19,696

NodeJS: コールバックに戻すと 4.8 倍高速になります!

Gemma Black9m2024/02/12
Read on Terminal Reader

コールバックは、非同期/待機を並行して実行すると 4.8 倍高速になります。また、連続コールバックを実行すると、わずか 1.9 倍高速になります。
featured image - NodeJS: コールバックに戻すと 4.8 倍高速になります!
Gemma Black HackerNoon profile picture

はい、言いました!


コールバックは、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 はメインの Node.js 実行可能スレッドの外側にあるキューに I/O タスクを渡すということです。このキューは次のように実行されます純粋なC 。多数のスレッドがこれらの I/O 操作を処理できる可能性があります。そこで I/O を処理する Node.js が威力を発揮します。


ただし、Promise はメインの単一の実行可能スレッドで処理されます。そして、async/await は確かに約束されていますが、ブロックが追加されました。

6 つの異なるキューで構成されるイベント ループ。出典: https://www.builder.io/blog/visual-guide-to-nodejs-event-loop

では、コールバックは Promise よりも速いのでしょうか?

テストしてみましょう。


最初に。私のマシン!協力することの補足カンマ。どのようなリソースを使用しているのかに注目することが重要です。メモリもCPUもたっぷり。

 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 は 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 の使用をさらに検討するつもりです。


ここにも登場します。