paint-brush
NodeJS: 콜백으로 돌아가면 4.8배 더 빨라집니다!by@gemmablack
19,700
19,700

NodeJS: 콜백으로 돌아가면 4.8배 더 빨라집니다!

Gemma Black9m2024/02/12
Read on Terminal Reader

콜백은 async/await를 병렬로 실행하면 4.8배 더 빠릅니다. 순차 콜백을 실행하면 1.9배 더 빨라집니다.
featured image - NodeJS: 콜백으로 돌아가면 4.8배 더 빨라집니다!
Gemma Black HackerNoon profile picture

응, 내가 말했잖아!


콜백은 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가 기본 Node.js 실행 스레드 외부에 있는 대기열에 I/O 작업을 전달한다는 것입니다. 이 대기열은 다음에서 실행됩니다. 순수 C . 많은 스레드가 잠재적으로 이러한 I/O 작업을 처리할 수 있습니다. I/O를 처리하면서 Node.js가 빛을 발할 수 있는 부분이 바로 여기입니다.


그러나 약속은 기본 단일 실행 스레드에서 처리됩니다. 그리고 async/await는 약속이지만 이제 차단 기능이 추가되었습니다.

6개의 서로 다른 큐로 구성된 이벤트 루프. 출처: https://www.builder.io/blog/visual-guide-to-nodejs-event-loop

그렇다면 콜백이 프라미스보다 빠를까요?

테스트해 보겠습니다.


우선. 내 기계 ! 협력의 보완 카르마 . 우리가 어떤 리소스를 사용하고 있는지 기록하는 것이 중요합니다. 충분한 메모리와 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를 사용하여 이 텍스트 파일을 읽을 것입니다. 즉, 우주에서 가장 무거운 개체로 인해 속도가 느려지는 것을 원하지 않기 때문에 노드 모듈 종속성이 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가 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을 더 많이 사용하게 될 것입니다.


여기에도 나타납니다.