응, 내가 말했잖아!
콜백은 async/await를 병렬로 실행하는 경우 4.8배 더 빠릅니다. 순차 콜백을 실행하면 속도가 1.9배 더 빨라집니다.
내 의심스러운 테스트에 대한 도움이 되고 친절한 의견을 받은 후 이 기사를 다소 수정했습니다. 😂🙏
감사합니다
리카르도 로페즈 그리고라이언 포 벤치마크를 올바른 방향으로 이끌기 위해 시간을 투자합니다. 나의 첫 번째 실수는 실제로 코드 실행이 완료될 때까지 기다리지 않았다는 것입니다. 이로 인해 결과가 미친 듯이 왜곡되었습니다. 두 번째는 병렬 런타임과 순차 런타임을 비교했기 때문에 벤치마크가 쓸모없게 되었습니다.
그래서 이것은 나의 초기 오류를 해결하는 2라운드입니다. 이전에 나는 이렇게 말했습니다.
원래는 콜백으로 돌아가면 NodeJS가 34.7배 더 빠르다고 썼습니다! 🤣 틀렸어.
이전의 나쁜 벤치마크만큼 인상적이지는 않지만(맥락에 대한 설명 참조) 여전히 상당한 차이가 있습니다.
파일을 읽을 때 콜백을 promise 및 async/await와 10,000번 비교했습니다. 어쩌면 그것은 어리석은 테스트일지도 모르지만 I/O에서 어느 것이 더 빠른지 알고 싶었습니다.
그런 다음 마침내 Node.js의 콜백을 Go!와 비교했습니다.
자, 누가 이겼는지 맞춰보세요?
나는 심술궂지 않을 것이다. TLDR . 골랑!
낮을수록 좋습니다. 결과는
ms
단위입니다.
자, 진정한 벤치마커요? 이것에 대해 나에게 진정하십시오. 하지만 제가 더 나은 사람이 될 수 있도록 여러분의 의견을 남겨주세요.
그리고 그것은 나를 괴롭힌다.
느리다는 게 무슨 뜻인가요? 모든 벤치마크와 마찬가지로 내 벤치마크도 상황에 따라 다릅니다.
나는 다음에 대해 읽기 시작했습니다.
하지만 제가 이해한 가장 중요한 점은 Node.js가 기본 Node.js 실행 스레드 외부에 있는 대기열에 I/O 작업을 전달한다는 것입니다. 이 대기열은 다음에서 실행됩니다.
그러나 약속은 기본 단일 실행 스레드에서 처리됩니다. 그리고 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를 사용하여 이 텍스트 파일을 읽을 것입니다. 즉, 우주에서 가장 무거운 개체로 인해 속도가 느려지는 것을 원하지 않기 때문에 노드 모듈 종속성이 0이라는 의미입니다.
먼저 병렬 콜백부터 시작해 보겠습니다. 나는 동일한 파일을 가능한 한 빨리, 동시에 얼마나 빨리 읽을 수 있는지에 관심이 있습니다. 그리고 병렬보다 빠른 것은 무엇입니까?
// > 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) });
그런 다음 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가 느립니다. 그래도 300ms 미만의 파일 10,000개는 비웃을 수 없습니다. 하지만 저는 Go의 속도에 겸손해졌습니다!
나는 정말로 끔찍한 벤치마크를 가지고 있었습니다. Ricardo와 Ryan에게 다시 한 번 감사드립니다.
그래, 내가 했어. 이제 그들은 더 나아졌기를 바랍니다.
하지만 실제로 누가 같은 파일을 반복해서 읽을 것인가? 그러나 사물 간의 상대적인 테스트를 위해서는 이것이 도움이 되는 비교가 되기를 바랍니다.
또한 Node.js가 얼마나 많은 스레드를 사용하고 있는지 모릅니다.
내 CPU 코어가 Go와 Node.js 성능에 어떤 영향을 미치는지 모르겠습니다.
코어가 하나인 AWS 머신을 빌려 비교해 보면 됩니다.
제가 Mac M1을 사용하고 있기 때문인가요?
Node.js는 Linux나 Windows에서 어떻게 작동할까요? 😱
그리고 실용성이 있습니다. 예, 파일을 읽는 것이 한 가지이지만 어떤 시점에서는 파일의 데이터로 작업을 수행하려면 파일을 읽을 때까지 기다려야 합니다. 따라서 메인 스레드의 속도는 여전히 매우 중요합니다.
내 말은, 당신은 정말로 정말로 그러기를 원하나요?
모르겠습니다. 나는 누구에게도 무엇을 해야할지 말하고 싶지 않습니다.
하지만 나는 async/awaits의 깔끔한 구문을 좋아합니다.
더 좋아 보이네요.
그들은 더 잘 읽습니다.
주관적이라는 건 더 잘 알지만 콜백 지옥이 기억나고 약속이 생겼을 때 감사했습니다. 자바스크립트를 견딜 수 있게 만들었습니다.
이제 Golang은 콜백과 async/await를 사용하여 최적의 상태에서 Node.js보다 확실히 9.2배 빠릅니다! 따라서 좋은 가독성과 성능을 원한다면 Golang이 승자입니다. 하지만 저는 Golang이 내부적으로 어떻게 보이는지 배우고 싶습니다.
누구라도. 이것은 재미있었습니다. 이벤트 루프에서 콜백과 I/O가 작동하는 방식을 이해하는 데 도움이 되는 연습에 가깝습니다.
Node.js가 느린가요? 아니면 느린 모드에서 Node.js를 사용하고 있나요?
아마도 성능이 중요한 곳에서는 Golang이 뛰어들 가치가 있습니다. 앞으로는 Golang을 더 많이 사용하게 될 것입니다.
여기에도 나타납니다.