是的,我说过了!
并行运行回调时,回调速度比并行运行 async/await 快4.8 倍。当我们运行顺序回调时,速度仅提高了1.9 倍。
在我收到一些关于我的不可靠测试的有用和善意的评论后,我对这篇文章进行了一些修改。 😂🙏
谢谢至
里卡多·洛佩斯 和瑞恩·坡 感谢您花时间将基准引向正确的方向。我的第一个失误是我实际上并没有等待代码执行完成,这严重扭曲了结果。第二个是我正在比较并行运行时和顺序运行时,这使得基准测试毫无价值。
这是第二轮,它解决了我最初的错误。之前我说过:
最初,我写道,如果我们回到回调,NodeJS 的速度会快 34.7 倍! 🤣 错了。
不像我之前的糟糕基准那么令人印象深刻(并查看上下文的评论),但仍然有相当大的差异。
我在读取文件时将回调与 Promise 和 async/await 进行了 10,000 次比较。也许这是一个愚蠢的测试,但我想知道,哪个 I/O 速度更快。
然后我终于将 Node.js 中的回调与 Go 进行了比较!
现在,猜猜谁赢了?
我不会刻薄的。太长了。戈朗!
越低越好。结果以
ms
。
现在,真正的基准测试者?在这件事上对我宽容一些。但请留下您的评论,让我成为一个更好的人。
这让我很烦恼。
因为慢意味着什么?与所有基准一样,我的基准是上下文相关的。
但我主要了解的是,Node.js 将 I/O 任务传递到位于 Node.js 主可执行线程外部的队列上。该队列运行于
然而,承诺是在主单个可执行线程中处理的。异步/等待,很好,承诺但现在添加了阻塞。
让我们来测试一下。
首先。我的机器!与他人合作的补充
MacBook Pro (14-inch, 2021) Chip Apple M1 Pro Memory 32 GB Cores 10 NodeJS v20.8.1
所以我们有一个text.txt
文件,其中包含原始消息Hello, world
。
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() } }) } });
其次,我们再次有回调,但是是顺序的(或者更确切地说是阻塞的)。我感兴趣的是顺序读取同一文件的速度。已经很久没有进行回调调用回调了,再次尝试很有趣。虽然,看起来并不漂亮。
// > 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) });
然后我们有异步/等待。我最喜欢的使用 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 的性能。
我可以租一台只有一个核心的 AWS 机器并进行比较。
是因为我用的是 Mac M1 吗?
Node.js 在 Linux 或 Windows 上的执行情况如何? 😱
还有一个实用性,是的,读取文件是一回事,但在某些时候,您必须等待读取文件才能对文件中的数据执行某些操作。因此,主线程的速度仍然非常重要。
我的意思是,你真的真的想要吗?
我不知道。我绝对不想告诉任何人该怎么做。
但我喜欢 async/await 简洁的语法。
他们看起来更好了。
他们读得更好。
我知道这里的更好是主观的,但我记得回调地狱,当承诺出现时我很感激。它让 Javascript 变得可以忍受。
现在,Golang 在最佳状态下明显比 Node.js 快,在回调和异步/等待方面,快了 9.2 倍!因此,如果我们想要良好的可读性和性能,Golang 是赢家。尽管如此,我很想了解 Golang 的底层是什么样的。
任何人。这很有趣。这更多的是帮助我理解回调和 I/O 在事件循环中如何工作的练习。
Node.js 慢吗?或者我们只是在慢速模式下使用 Node.js?
也许在性能很重要的地方,Golang 值得一跳。将来我肯定会更多地考虑使用 Golang。
也出现在这里。