2023 Node.js 성능 현황

hwisaac·2023년 9월 16일
2

https://blog.rafaelgss.dev/state-of-nodejs-performance-2023

Node.js 성능 현황 2023

2023년이며 Node.js v20를 출시했습니다. 이것은 중요한 성과이며, 이 문서는 Node.js의 성능 상태를 과학적인 숫자를 사용하여 평가하는 데 목적을 두고 있습니다.

모든 벤치마크 결과에는 재현 가능한 예제와 하드웨어 세부 정보가 포함되어 있습니다. 일반 독자들을 위해 노이즈를 줄이기 위해 재현 단계는 모든 섹션의 시작에서 축소될 것입니다.

이 문서는 Node.js의 다른 버전을 비교 분석하기 위한 것입니다. 개선 사항과 문제점을 강조하고 그러한 변경 사항의 이유에 대한 통찰력을 제공하지만 다른 JavaScript 런타임과 비교하지는 않습니다.

이 실험을 수행하기 위해 Node.js 버전 16.20.018.16.0 및 20.0.0를 사용하고 벤치마크 스위트를 세 가지 다른 그룹으로 나눴습니다:

  1. Node.js 내부 벤치마크

Node.js 벤치마크 스위트의 크기가 크고 시간이 오래 걸리므로 Node.js 개발자 및 설정에 더 큰 영향을 미치는 것으로 판단되는 벤치마크만을 선택적으로 선택했습니다. 예를 들어 fs.readfile를 사용하여 16 MB 파일을 읽는 것과 같은 모듈별로 그룹화된 벤치마크입니다. Node.js 벤치마크 스위트에 대한 자세한 내용은 Node.js 소스 코드를 참조하십시오.

  1. nodejs-bench-operations

nodejs-bench-operations라는 저장소를 관리하며 이 저장소에는 Node.js의 모든 주요 버전 및 각 버전 라인의 마지막 세 개의 릴리스에 대한 벤치마크 작업이 포함되어 있습니다. 이를 통해 Node.js v16.20.0과 v18.16.0 또는 v19.8.0과 v19.9.0과 같은 다른 버전 간의 결과를 쉽게 비교할 수 있으며 Node.js 코드베이스의 회귀를 식별하기 위한 목적입니다. Node.js 비교에 관심이 있는 경우 이 저장소를 팔로우하면 도움이 될 것입니다 (도움이 되는 경우 스타를 주는 것을 잊지 마세요).

  1. HTTP 서버 (프레임워크)

이 실용적인 HTTP 벤치마크는 다양한 라우트로 많은 요청을 보내고 JSON, 일반 텍스트 및 오류를 반환하여 참조로 express 와 fastify를 사용합니다. 주요 목표는 Node.js 내부 벤치마크와 nodejs-bench-operations의 결과가 일반 HTTP 애플리케이션에 적용 가능한지 여부를 결정하는 것입니다.

💡 업데이트: 이 문서에서 다루는 내용이 너무 많기 때문에 세 번째 및 마지막 단계는 후속 문서에서 공유될 예정입니다. 최신 정보를 받으려면 Twitter/LinkedIn에서 저를 팔로우하고 알림을 받도록 권장합니다.

환경

이 벤치마크를 수행하기 위해 다음과 같은 컴퓨팅 최적화 인스턴스를 사용하는 AWS Dedicated Host를 사용했습니다:

  • c6i.xlarge (Ice Lake) 3.5 GHz - 컴퓨팅 최적화
  • 4 vCPU
  • 8 GB 메모리
  • Canonical, Ubuntu, 22.04 LTS, amd64 jammy
  • 1GiB SSD 볼륨 유형

Node.js 내부 벤치마크

이 벤치마크에서 선택한 모듈/네임 스페이스는 다음과 같습니다:

  • fs - Node.js 파일 시스템
  • events - Node.js 이벤트 클래스 EventEmitter / EventTarget
  • http - Node.js HTTP 서버 + 파서
  • misc - child_processesworker_threads + trace_events를 사용한 Node.js 시작 시간 + module.require
  • streams - Node.js 스트림 생성, 파괴, 읽기 및 기타
  • url - Node.js URL 파서
  • buffers - Node.js 버퍼 작업
  • util - Node.js 텍스트 인코더/디코더

그리고 사용된 구성은 RafaelGSS/node#state-of-nodejs에서 확인할 수 있으며 모든 결과는 주요 리포지토리에 게시되었습니다: Node.js 성능 현황 2023.

Node.js 벤치마크 접근 방식

결과를 제시하기 전에 벤

치마크 결과의 신뢰성을 확인하는 데 사용한 통계적 접근 방식을 설명하는 것이 중요합니다. 이 방법은 이전 블로그 게시물에서 자세히 설명되어 있으며 다음에서 확인할 수 있습니다: 벤치마크 준비 및 평가.

새로운 Node.js 버전의 영향을 비교하기 위해 각 구성 및 Node.js 16, 18 및 20에서 각 벤치마크를 여러 번 (30회) 실행했습니다. 표시되는 출력에서 주의해야 할 두 가지 열이 있습니다.

  1. 개선 - 새 버전 대비 개선된 백분율
  2. 신뢰도 - 개선을 유효화할 충분한 통계적 증거가 있는지 여부

예를 들어 다음 표 결과를 고려해 보십시오.

                                                                              신뢰도     개선       정확도 (*)   (**)  (***)
fs/readfile.js concurrent=1 len=16777216 encoding='ascii' duration=5                 ***     67.59 %       ±3.80% ±5.12% ±6.79%
fs/readfile.js concurrent=1 len=16777216 encoding='utf-8' duration=5                 ***     11.97 %       ±1.09% ±1.46% ±1.93%
fs/writefile-promises.js concurrent=1 size=1024 encodingType='utf' duration=5                 0.36 %       ±0.56% ±0.75% ±0.97%

많은 비교를 수행할 때 거짓 양성 결과의 위험이 높아집니다.
이 경우 10개의 비교가 있으므로 다음과 같은 거짓 양성 결과가 발생할 수 있습니다:
  0.50 거짓 양성 결과 (*, **, ***을 고려할 때),
  0.10 거짓 양성 결과 (**, ***을 고려할 때),
  0.01 거짓 양성 결과 (***을 고려할 때)

거기에 따르면 fs.readfile가 Node.js 16에서 Node.js 18로 개선되지 않을 위험이 0.1% (신뢰도 ***) 있습니다. 따라서 결과에 대해 상당히 확신할 수 있습니다. 표 구조는 다음과 같이 읽을 수 있습니다.

  • fs/readfile.js - 벤치마크 파일
  • concurrent=1 len=16777216 encoding='ascii' duration=5 - 벤치마크 옵션. 각 벤치마크 파일에는 많은 옵션이 있을 수 있으며 이 경우 1개의 동시 파일을 5초 동안 ASCII 인코딩을 사용하여 16777216바이트를 읽는 것입니다.

통계적으로 열정적인 사람들을 위해 스크립트는 두 버전 모두 성능이 동일하다는 귀무 가설로 [독립/비독립 2 그룹 t-테스트](https://en.wikipedia.org/wiki/Student%27s_t-test#Equal_or_unequal_sample_sizes%2C_unequal_variances%28sX1%3E_2sX2_or_sX2%3E2sX1%29)를 수행합니다. 신뢰도 필드는 p-값이 0.05보다 작으면 별표를 표시합니다. — 벤치마크 작성 및 실행

벤치마크 설정

  1. Node.js 리포지토리를 포크(clone)합니다.
  2. state-of-nodejs 브랜치를 체크아웃합니다.
  3. Node.js 16, 18 및 20 이진 파일을 생성합니다.
  4. benchmark.sh 스크립트를 실행합니다.
#1
git clone git@github.com:RafaelGSS/node.git
#2
cd node && git checkout state-of-nodejs
#3
nvm install v20.0.0
cp $(which node) ./node20
nvm install v18.16.0
cp $(which node) ./node18
nvm install v16.20.0
cp $(which node) ./node16
#4
./benchmark.sh

파일 시스템

Node.js를 16에서 18로 업그레이드할 때, fs.readfile API를 사용하여 ascii 인코딩을 사용하여 파일을 읽을 때 67% 정도 개선되었으며, utf-8을 사용할 때는 대략 12% 정도 개선되었습니다.

readfile 비교 v16 및 v18

벤치마크 결과는 Node.js 16에서 18로 업그레이드할 때 fs.readfile API를 ascii 인코딩을 사용하여 약 67% 개선되었으며, utf-8을 사용할 때는 약 12% 정도 개선되었습니다. 벤치마크에 사용된 파일은 다음 코드 스니펫을 사용하여 생성되었습니다.

const data = Buffer.alloc(16 * 1024 *

 1024, 'x');
fs.writeFileSync(filename, data);

그러나 Node.js 20에서 ascii를 사용하여 fs.readfile을 사용할 때 27% 정도 개선 사항이 있었습니다. 이 개선 사항은 Node.js 성능 팀에 보고되었으며 수정될 것으로 예상됩니다. 반면에 fs.opendir, fs.realpath, 및 fs.readdir은 Node.js 18에서 Node.js 20으로 개선되었습니다. Node.js 18과 20 사이의 비교는 다음과 같이 벤치마크 결과에서 확인할 수 있습니다.

                                                                              신뢰도     개선       정확도 (*)   (**)  (***)
fs/bench-opendir.js bufferSize=1024 mode='async' dir='test/parallel' n=100           ***      3.48 %       ±0.22% ±0.30% ±0.39%
fs/bench-opendir.js bufferSize=32 mode='async' dir='test/parallel' n=100             ***      7.86 %       ±0.29% ±0.39% ±0.50%
fs/bench-readdir.js withFileTypes='false' dir='test/parallel' n=10                   ***      8.69 %       ±0.22% ±0.30% ±0.39%
fs/bench-realpath.js pathType='relative' n=10000                                     ***      5.13 %       ±0.97% ±1.29% ±1.69%
fs/readfile.js concurrent=1 len=16777216 encoding='ascii' duration=5                 ***    -27.30 %       ±4.27% ±5.75% ±7.63%
fs/readfile.js concurrent=1 len=16777216 encoding='utf-8' duration=5                 ***      3.25 %       ±0.61% ±0.81% ±1.06%

  0.05%의 리스크를 고려할 때 거짓 양성 결과 (***),
  0.01%의 리스크를 고려할 때 거짓 양성 결과 (**, ***),
  0.001%의 리스크를 고려할 때 거짓 양성 결과 (***)

Node.js 16을 사용하는 경우 Node.js 16과 Node.js 20 사이의 다음 비교를 사용할 수 있습니다.

                                                                              신뢰도     개선       정확도 (*)    (**)   (***)
fs/bench-opendir.js bufferSize=1024 mode='async' dir='test/parallel' n=100           ***      2.79 %       ±0.26%   ±0.35%   ±0.46%
fs/bench-opendir.js bufferSize=32 mode='async' dir='test/parallel' n=100             ***      5.41 %       ±0.27%   ±0.35%   ±0.46%
fs/bench-readdir.js withFileTypes='false' dir='test/parallel' n=10                   ***      2.19 %       ±0.26%   ±0.35%   ±0.45%
fs/bench-realpath.js pathType='relative' n=10000                                     ***      6.86 %       ±0.94%   ±1.26%   ±1.64%
fs/readfile.js concurrent=1 len=16777216 encoding='ascii' duration=5                 ***     21.96 %       ±7.96%  ±10.63%  ±13.92%
fs/readfile.js concurrent=1 len=16777216 encoding='utf-8' duration=5                 ***     15.55 %       ±1.09%   ±1.46%   ±1.92%

readfile 비교 16 및 20

이벤트

EventTarget 클래스는 이벤트 측면에서 가장 큰 개선 사항을 보여주었습니다. 이 벤치마크는 EventTarget.prototype.dispatchEvent(new Event('foo'))를 사용하여 백만 개의 이벤트를 디스패치하는 것을 포함합니다.

Node.js 16에서 Node.js 18로 업그레이드하면 이벤트 디스패치 성능이 약 15% 정도 개선될 수 있습니다. 그러나 Node.js 18에서 Node.js 20으로 업그레이드하면 단일 리스너만 있는 경우 성능이 최대 200% 개선될 수 있습니다.

이벤트타겟 비교 18 및 20

EventTarget 클래스는 Web API의 중요한 구성 요소이며 AbortSignalworker_threads와 같은 다양한 부모 기능에서 사용됩니다. 따라서 이 클래스에 대한 최적화는 fetchAbortController를 포함한 이러한 기능의 성능에 영향을 미칠 수 있습니다. 또한 EventEmitter.prototype.emit API도 Node.js 16에서 Node.js 20으로 비교할 때 약 11.5% 정도 개선되었습니다. 자세한 비교 내용은 다음과 같습니다.

                                                                 신뢰도     개선       정확도 (*)   (**)  (***)
events/ee-emit.js listeners=5 argc=2 n=2000000                          ***     11.49 %       ±1.37% ±1.83% ±2.38%
events/ee-once.js argc=0 n=20000000                                     ***     -4.35 %       ±0.47% ±0.62% ±0.81%
events/eventtarget-add-remove.js nListener=10 n=1000000                 ***      3.80 %       ±0.83% ±1.11% ±1.46%
events/eventtarget-add-remove.js nListener=5 n=1000000                  ***      6.41 %       ±1.54% ±2.05% ±2.67%
events/eventtarget.js listeners=1 n=1000000                             ***    259.34 %       ±2.83% ±3.81% ±5.05%
events/eventtarget.js listeners=10 n=1000000                            ***    176.98 %       ±1.97% ±2.65% ±3.52%
events/eventtarget.js listeners=5 n=1000000                             ***    219.14 %       ±2.20% ±2.97% ±3.94%

HTTP

HTTP 서버는 Node.js의 성능 개선에서 가장 영향력 있는 레이어 중 하나입니다. 현재 대부분의 Node.js 애플리케이션은 HTTP 서버를 실행하는 것이 사실이므로 어떤 변경이라도 쉽게 semver-major로 간주되고 성능을 향상시키기 위한 호환성 향상 노력이 증가할 수 있습니다.

따라서 사용된 HTTP 서버는 각 요청에 'C'를 포함하는 256바이트의 4개의 청크를 응답하는 http.Server입니다. 다음 예제에서 확인할 수 있습니다.

http.createServer((req, res) => {
    const n_chunks = 4;
    const body = 'C'.repeat();
    const len = body.length;
		res.writeHead(200, {
				'Content-Type': 'text/plain',
		    'Content-Length': len.toString()
		});
    for (i = 0, n = (n_chunks - 1); i < n; ++i)
      res.write(body.slice(i * step, i * step + step));
    res.end(body.slice((n_chunks - 1) * step));
})
// See: https://github.com/nodejs/node/blob/main/benchmark/fixtures/simple-http-server.js

Node.js 16과 Node.js 18의 성능을 비교하면 약 8% 정도의 개선이 있습니다. 그러나 Node.js 18에서 Node.js 20으로 업그레이드하면 성능이 크게 개선되어 96.13% 정도로 나타납니다.

[http/simple 비교 18 및 20](https://res.cloudinary.com/rafaelgss/image/upload/v168417

4353/blog/state-of-nodejs-performance-2023/Untitled_rjsskx.png)

이러한 벤치마크 결과는 test-double-http 벤치마커 방법을 사용하여 수집되었습니다. 이 방법은 HTTP GET 요청을 보내는 간단한 Node.js 스크립트로, 다음과 같습니다.

function run() {
  if (http.get) { // HTTP or HTTPS
    if (options) {
      http.get(url, options, request);
    } else {
      http.get(url, request);
    }
  } else { // HTTP/2
    const client = http.connect(url);
    client.on('error', () => {});
    request(client.request(), client);
  }
}

run();

더 신뢰할 만한 벤치마킹 도구인 autocannon 또는 wrk로 전환하면 보고된 개선 사항이 크게 감소한 것을 관찰했습니다. 이는 이전의 벤치마킹 방법에 제한 사항 또는 오류가 있었음을 나타냅니다. 그러나 HTTP 서버의 실제 성능이 개선되었으며 새로운 벤치마킹 방법을 사용하여 개선 사항의 비율을 정확히 평가해야 합니다.

Express/Fastify 애플리케이션에서 96%/9% 성능 개선을 기대해야 하나요?

절대로 그렇지 않습니다. 프레임워크는 내부 HTTP API를 사용하지 않을 수 있습니다. 이것이 Fastify가 빠른 이유 중 하나입니다! 이러한 이유로 이 보고서에서는 다른 벤치마크 스위트 (3. HTTP 서버)도 고려되었습니다.

기타

저희의 테스트에 따르면 startup.js 스크립트는 Node.js 프로세스 라이프사이클에서 상당한 개선을 보여주며, Node.js 버전 18에서 버전 20으로 업그레이드할 때 27%의 성능 향상이 관찰되었습니다. 이 개선은 Node.js 버전 16과 비교했을 때 시동 시간이 34.75% 감소한 경우에 더욱 빛을 발합니다!

현대 애플리케이션은 점점 서버리스 시스템에 의존하므로 시작 시간을 줄이는 것이 전체 성능을 향상시키는 중요한 요소가 되었습니다. Node.js 팀은 항상 플랫폼의 이 측면을 최적화하기 위해 노력하고 있으며 이를 나타내는 것은 다음과 같은 전략적 이니셔티브입니다: https://github.com/nodejs/node/issues/35711.

이러한 시작 시간 개선은 서버리스 애플리케이션에만 도움이 되는 것이 아니라 빠른 부팅 시간을 필요로 하는 다른 Node.js 애플리케이션의 성능도 향상시킵니다. 이러한 업데이트는 모든 사용자를 위해 플랫폼의 속도와 효율성을 향상시키기 위한 Node.js 팀의 헌신을 보여줍니다.

$ node-benchmark-compare compare-misc-16-18.csv
                                                                                     confidence improvement accuracy (*)   (**)  (***)
misc/startup.js count=30 mode='process' script='benchmark/fixtures/require-builtins'        ***     12.99 %       ±0.14% ±0.19% ±0.25%
misc/startup.js count=30 mode='process' script='test/fixtures/semicolon'                    ***      5.88 %       ±0.15% ±0.20% ±0.26%
misc/startup.js count=30 mode='worker' script='benchmark/fixtures/require-builtins'         ***      5.26 %       ±0.14% ±0.19% ±0.25%
misc/startup.js count=30 mode='worker' script='test/fixtures/semicolon'                     ***      3.84 %       ±0.15% ±0.21% ±0.27%

$ node-benchmark-compare compare-misc-18-20.csv
                                                                                     confidence improvement accuracy (*)   (**)  (***)
misc/startup.js count=30 mode='process' script='benchmark/fixtures/require-builtins'        ***     -4.80 %       ±0.13% ±0.18% ±0.23%
misc/startup.js count=30 mode='process' script='test/fixtures/semicolon'                    ***     27.27 %       ±0.22% ±0.29% ±0.38%
misc/startup.js count=30 mode='worker' script='benchmark/fixtures/require-builtins'         ***      7.23 %       ±0.21% ±0.28% ±0.37%
misc/startup.js count=30 mode='worker' script='test/fixtures/semicolon'                     ***     31.26 %       ±0.33% ±0.44% ±0.58%

이 벤치마크는 상당히 간단합니다. [mode] 및 [script]를 사용하여 주어진 [script]를 사용하여 새 [mode]를 생성할 때 경과 시간을 측정합니다. [mode]는 다음과 같을 수 있습니다.

  • process - 새 Node.js 프로세스
  • worker - Node.js worker_thread

[script]는 다음과 같이 나뉩니다.

  • benchmark/fixtures/require-builtins - 모든 Node.js 모듈을 요구하는 스크립트
  • test/fixtures/semicolon - 빈 스크립트로, 단일 ; (세미콜론)을 포함합니다.

이 실험은 hyperfine 또는 time을 사용하여 쉽게 재현할 수 있습니다.

$ hyperfine --warmup 3 './node16 ./nodejs-internal-benchmark/semicolon.js'
Benchmark 1: ./node16 ./nodejs-internal-benchmark/semicolon.js
  Time (mean ± σ):      24.7 ms ±   0.3 ms    [User: 19.7 ms, System: 5.2 ms]
  Range (min … max):    24.1 ms …  25.6 ms    121 runs

$ hyperfine --warmup 3 './node18 ./nodejs-internal-benchmark/semicolon.js'
Benchmark 1: ./node18 ./nodejs-internal-benchmark/semicolon.js
  Time (mean ± σ):      24.1 ms ±   0.3 ms    [User: 18.1 ms, System: 6.3 ms]
  Range (min … max):    23.6 ms …  25.3 ms    123 runs

$ hyperfine --warmup 3 './node20 ./nodejs-internal-benchmark/semicolon.js'
Benchmark 1: ./node20 ./nodejs-internal-benchmark/semicolon.js
  Time (mean ± σ):      18.4 ms ±   0.3 ms    [User: 13.0 ms, System: 5.9 ms]
  Range (min … max):    18.0 ms …  19.7 ms    160 runs

💡 워마갑은 파일 시스템 캐시의 영향을 고려하기 위해 필요합니다.

trace_events 모듈도 Node.js 버전 16에서 버전 20으로 비교할 때 7%의 개선이 관찰되었습니다. 그러나 Node.js 버전 18과 버전 20을 비교할 때 이 개선은 2.39%로 약간 낮아졌음을 주목할 가치가 있습니다.

모듈

require() (또는 module.require)는 오랜 시간 동안 느린 Node.js 시작 시간의 원인 중 하나였습니다. 그러나 최근의 성능 개선은 이 함수도 최적화되었다는 것을 시사합니다. Node.js 버전 18과 20 사이에서 .js 파일을 요구할 때 4.20%, .json 파일을 요구할 때 6.58%, 디렉터리를 읽을 때 9.50%의 개선을 관찰했습니다. 이는 모두 시작 시간을 빠르게 하는 데 기여합니다.

require()를 최적화하는 것은 Node.js 애플리케이션에서 많이 사용되는 함수이므로 이 함수를 실행하는 데 걸리는 시간을 줄이면 전체 시작 프로세스를 크게 가속화하고 사용자 경험을 향상시킬 수 있습니다.

compare-module-18-20.png

스트림

스트림은 Node.js의 강력하고 널리 사용되는 기능 중 하나입니다. 그러나 Node.js 버전 16과 18 사이에서 스트림과 관련된 일부 작업이 느려졌습니다. 이에는 Duplex, Readable, Transform, Writable 스트림을 생성하고 소멸하는 것뿐만 아니라 Readable → Writable 스트림에 대한 .pipe() 메서드도 포함됩니다.

아래 그래프는 이 회귀를 보여줍니다.

compare-streams-16-18-streams-bar.png

그러나 Node.js 20에서 이 pipe 회귀가 줄어 들었습니다.

$ node-benchmark-compare compare-streams-18-20.csv
                                                       confidence improvement accuracy (*)   (**)  (***)
streams/creation.js kind='duplex' n=50000000                  ***     12.76 %       ±4.30% ±5.73% ±7.47%
streams/creation.js kind='readable' n=50000000                ***      3.48 %       ±1.16% ±1.55% ±2.05%
streams/creation.js kind='transform' n=50000000                **     -7.59 %       ±5.27% ±7.02% ±9.16%
streams/creation.js kind='writable' n=50000000                ***      4.20 %       ±0.87% ±1.16% ±1.53%
streams/destroy.js kind='duplex' n=1000000                    ***     -6.33 %       ±1.08% ±1.43% ±1.87%
streams/destroy.js kind='readable' n=1000000                  ***     -1.94 %       ±0.70% ±0.93% ±1.21%
streams/destroy.js kind='transform' n=1000000                 ***     -7.44 %       ±0.93% ±1.24% ±1.62%
streams/destroy.js kind='writable' n=1000000                           0.20 %       ±1.89% ±2.52% ±3.29%
streams/pipe.js n=5000000                                     ***     87.18 %       ±2.58% ±3.46% ±4.56%

그리고 알 수 있듯이 일부 유형의 스트림 (Transform 특히)은 Node.js 20에서 회귀되었습니다. 따라서 Node.js 16은 여전히이 구체적인 벤치마크에서 가장 빠른 스트림을 가지고 있으며, 이 벤치마크 결과를 'Node.js 18과 20에서 스트림이 너무 느리다!'고 읽지 마십시오. 이는 워크로드에 영향을 미칠 수도 미치지 않을 수도 있는 특정 벤치마크입니다. 예를 들어, 미진한 비교를 살펴보면 Node.js 20에서 다음 스니펫이 이전 버전보다 성능이 더 나은 것을 볼 수 있습니다.

suite.add('streams.Writable writing 1e3 * "some data"', function () {
  const writable = new Writable({
    write (chunk, enc, cb) {
      cb()
    }
  })

  let i = 0
  while(i < 1e3) {
    writable.write('some data')
    ++i
  }
})

사실, 인스턴스화 및 소멸 메서드는 Node.js 생태계에서 중요한 역할을 합니다. 따라서 일부 라이브러리에 부정적인 영향을 미칠 가능성이 매우 높습니다. 그러나 이 회귀는 Node.js 성능 WG에서 밀접하게 관찰되고 있습니다.

compare-streams-16-20-streams-bar.png

Readable 비동기 반복자는 Node.js 20에서 약 6.14% 더 빠릅니다.

URL

Node.js 18부터는 Node.js에 새로운 URL 파서 종속성인 Ada가 추가되었습니다. 이 변경으로 인해 URL을 구문 분석하는 Node.js의 성능이 크게 향상되었습니다. 일부 결과에서는 최대 400%의 개선이 나타날 수 있습니다. 평범한 사용자로서 직접 사용하지는 않겠지만 HTTP 서버를 사용하는 경우 이 성능 향상에 영향을 받을 가능성이 매우 높습니다.

URL 벤치마크 스위트는 상당히 크기 때문에 WHATWG URL 벤치마크 결과만 다룰 것입니다.

url.parse()url.resolve() 모두 폐기된 레거시 API이지만 여전히 일부 개발자가 사용하고 있습니다. Node.js 문서에서 인용한 내용입니다.

url.parse()는 URL 문자열을 구문 분석하는 데 관대하고 표준이 아닌 알고리즘을 사용합니다. 이것은 호스트 이름 스푸핑 및 사용자 이름 및 암호 처리와 같은 보안 문제에 취약합니다. 신뢰할 수 없는 입력과 함께 사용하지 마십시오. CVE(공개된 취약점)는 url.parse() 취약성에 대해 발행되지 않습니다. 대신 WHATWG URL API를 사용하십시오.

url.parseurl.resolve의 성능 변화에 대한 자세한 내용은 Node.js 성능 상태 2023 리포지토리를 확인하십시오.

새로운 whatwg-url-parse의 결과를 보는 것은 정말로 흥미로운데요:

compare-url-16-20-whatwg-bar.png

아래는 벤치마킹에 사용된 URL 목록입니다. 이 목록은 벤치마크 구성을 기반으로 선택되었습니다.

const urls = {
  long: 'http://nodejs.org:89/docs/latest/api/foo/bar/qua/13949281/0f28b/' +
        '/5d49/b3020/url.html#test?payload1=true&payload2=false&test=1' +
        '&benchmark=3&foo=38.38.011.293&bar=1234834910480&test=19299&3992&' +
        'key=f5c65e1e98fe07e648249ad41e1cfdb0',
  short: 'https://nodejs.org/en/blog/',
  idn: 'http://你好你好.在线',
  auth: 'https://user:pass@example.com/path?search=1',
  file: 'file:///foo/bar/test/node.js',
  ws: 'ws://localhost:9229/f46db715-70df-43ad-a359-7f9949f39868',
  javascript: 'javascript:alert("node is awesome");',
  percent: 'https://%E4%BD%A0/foo',
  dot: 'https://example.org/./a/../b/./c',
}

새로운 Ada 2.0 업그레이드로 인해 Node.js 20에서는 Node.js 18과 비교했을 때 상당한 개선이 있었습니다:

compare-url-18-20-whatwg-bar.png

벤치마크 파일은 매우 간단합니다:

function useWHATWGWithoutBase(data) {
  const len = data.length;
  let result = new URL(data[0]);  // Dead code elimination 방지
  bench.start();
  for (let i = 0; i < len; ++i) {
    result = new URL(data[i]);
  }
  bench.end(len);
  return result;
}

function useWHATWGWithBase(data) {
  const len = data.length;
  let result = new URL(data[0][0], data[0][1]);  // Dead code elimination 방지
  bench.start();
  for (let i = 0; i < len; ++i) {
    const item = data[i];
    result = new URL(item[0], item[1]);
  }
  bench.end(len);
  return result;
}

두 번째 매개변수는 URL을 생성/구문 분석할 때 기본값으로 사용됩니다. 또한 기본값이 전달되면 (withBase='true') 일반적인 사용 (new URL(data))보다 빠르게 수행되는 경향이 있습니다. 모든 결과를 주요 리포지토리에서 확인하십시오.

버퍼

Node.js에서는 버퍼를 사용하여 이진 데이터를 처리합니다. 버퍼는 메모리에 원시 이진 데이터를 저장하는 데 사용되는 내장 데이터 구조로, 네트워크 프로토콜, 파일 시스템 작업 또는 기타 저수준 작업과 함께 작업할 때 유용합니다. 전반적으로 버퍼는 Node.js의 중요한 부분이며 이진 데이터를 처리하기 위

해 플랫폼 전체에서 널리 사용됩니다.

Node.js 버퍼를 직접 또는 간접적으로 사용하는 경우 Node.js 20 초기 채용자들을 위해 좋은 소식이 있습니다.

Buffer.from()의 성능을 개선하는 것 외에도 Node.js 20은 Node.js 18에서 발생한 두 가지 주요 회귀를 수정했습니다.

  • Buffer.concat()

compare-buffers-16-18-concat-bar.png

Node.js 버전 20은 버전 18과 비교했을 때 상당한 개선을 보여주며, 이러한 개선 사항은 버전 16과 비교했을 때도 여전히 뚜렷하게 나타납니다:

compare-buffers-18-20-concat-bar.png

compare-buffers-16-20-concat-bar.png

  • Buffer.toJSON()

Node.js 16에서 Node.js 18로 이동할 때 Buffer.toJSON의 성능이 88% 감소한 것으로 나타났습니다:

$ node-benchmark-compare compare-buffers-16-18.csv
                                                                            confidence improvement accuracy (*)    (**)   (***)
buffers/buffer-tojson.js len=256 n=10000                                           ***    -81.12 %       ±1.25%  ±1.69%  ±2.24%
buffers/buffer-tojson.js len=4096 n=10000                                          ***    -88.39 %       ±0.69%  ±0.93%  ±1.23%

그러나 Node.js 20에서 이 회귀는 몇 배로 개선되었습니다!

$ node-benchmark-compare compare-buffers-18-20.csv
                                                                            confidence improvement accuracy (*)    (**)   (***)
buffers/buffer-tojson.js len=256 n=10000                                           ***    482.81 %       ±7.02% ±9.42% ±12.42%
buffers/buffer-tojson.js len=4096 n=10000                                          ***    763.34 %       ±5.22% ±7.04%  ±9.34%

따라서 Node.js 20은 버퍼 처리에서 가장 빠른 Node.js 버전임을 말할 수 있습니다.

Node.js 20과 Node.js 18 간의 전체 비교는 다음과 같습니다:

$ node-benchmark-compare compare-buffers-18-20.csv
                                                                            confidence improvement accuracy (*)   (**)   (***)
buffers/buffer-base64-decode.js size=8388608 n=32                                  ***      1.66 %       ±0.10% ±0.14%  ±0.18%
buffers/buffer-base64-encode.js n=32 len=67108864                                  ***     -0.44 %       ±0.17% ±0.23%  ±0.30%
buffers/buffer-compare.js n=1000000 size=16                                        ***     -3.14 %       ±0.82% ±1.09%  ±1.41%
buffers/buffer-compare.js n=1000000 size=16386                                     ***    -15.56 %       ±5.97% ±7.95% ±10.35%
buffers/buffer-compare.js n=1000000 size=4096                                              -2.63 %       ±3.09% ±4.11%  ±5.35%
buffers/buffer-compare.js n=1000000 size=512                                       ***     -6.15 %       ±1.28% ±1.71%  ±2.24%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16          ***    300.67 %       ±0.71% ±0.95%  ±1.24%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4           ***    212.56 %       ±4.81% ±6.47%  ±8.58%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16         ***    287.63 %       ±2.47% ±3.32%  ±4.40%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4          ***    216.54 %       ±1.24% ±1.66%  ±2.17%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16        ***     38.44 %       ±1.04% ±1.38%  ±1.80%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4         ***     91.52 %       ±3.26% ±4.38%  ±5.80%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16          ***    192.63 %       ±0.56% ±0.74%  ±0.97%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4           ***    157.80 %       ±1.52% ±2.02%  ±2.64%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16         ***    188.71 %       ±2.33% ±

3.12%  ±4.10%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4          ***    151.18 %       ±1.13% ±1.50%  ±1.96%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16        ***     20.83 %       ±1.29% ±1.72%  ±2.25%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4         ***     59.13 %       ±3.18% ±4.28%  ±5.65%
buffers/buffer-from.js n=800000 len=100 source='array'                             ***      3.91 %       ±0.50% ±0.66%  ±0.87%
buffers/buffer-from.js n=800000 len=100 source='arraybuffer-middle'                ***     11.94 %       ±0.65% ±0.86%  ±1.13%
buffers/buffer-from.js n=800000 len=100 source='arraybuffer'                       ***     12.49 %       ±0.77% ±1.03%  ±1.36%
buffers/buffer-from.js n=800000 len=100 source='buffer'                            ***      7.46 %       ±1.21% ±1.62%  ±2.12%
buffers/buffer-from.js n=800000 len=100 source='object'                            ***     12.70 %       ±0.84% ±1.12%  ±1.47%
buffers/buffer-from.js n=800000 len=100 source='string-base64'                     ***      2.91 %       ±1.40% ±1.88%  ±2.46%
buffers/buffer-from.js n=800000 len=100 source='string-utf8'                       ***     12.97 %       ±0.77% ±1.02%  ±1.33%
buffers/buffer-from.js n=800000 len=100 source='string'                            ***     16.61 %       ±0.71% ±0.95%  ±1.25%
buffers/buffer-from.js n=800000 len=100 source='uint16array'                       ***      5.64 %       ±0.84% ±1.13%  ±1.48%
buffers/buffer-from.js n=800000 len=100 source='uint8array'                        ***      6.75 %       ±0.95% ±1.28%  ±1.68%
buffers/buffer-from.js n=800000 len=2048 source='array'                                     0.03 %       ±0.33% ±0.43%  ±0.56%
buffers/buffer-from.js n=800000 len=2048 source='arraybuffer-middle'               ***     11.73 %       ±0.55% ±0.74%  ±0.96%
buffers/buffer-from.js n=800000 len=2048 source='arraybuffer'                      ***     12.85 %       ±0.55% ±0.73%  ±0.96%
buffers/buffer-from.js n=800000 len=2048 source='buffer'                           ***      7.66 %       ±1.28% ±1.70%  ±2.21%
buffers/buffer-from.js n=800000 len=2048 source='object'                           ***     11.96 %       ±0.90% ±1.20%  ±1.57%
buffers/buffer-from.js n=800000 len=2048 source='string-base64'                    ***      4.10 %       ±0.46% ±0.61%  ±0.79%
buffers/buffer-from.js n=800000 len=2048 source='string-utf8'                      ***     -1.30 %       ±0.71% ±0.96%  ±1.27%
buffers/buffers/node.js n=800000 len=2048 source='string'                          ***     -2.23 %       ±0.93% ±1.25%  ±1.64%
buffers/buffer-from.js n=800000 len=2048 source='uint16array'                      ***      6.89 %       ±1.44% ±1.91%  ±2.49%
buffers/buffer-from.js n=800000 len=2048 source='uint8array'                       ***      7.74 %       ±1.36% ±1.81%  ±2.37%
buffers/buffer-tojson.js len=0 n=10000                                             ***    -11.63 %       ±2.34% ±3.11%  ±4.06%
buffers/buffer-tojson.js len=256 n=10000                                           ***    482.81 %       ±7.02% ±9.42% ±12.42%
buffers/buffer-tojson.js len=4096 n=10000                                          ***    763.34 %       ±5.22% ±7.04%  ±9.34%

따라서 Node.js 20과 Node.js 18 간의 성능 비교 결과를 확인할 수 있습니다.

텍스트 인코딩 및 디코딩

TextDecoder와 TextEncoder는 웹 API 명세의 일부로, 현대 웹 브라우저와 Node.js에서 사용할 수 있는 두 가지 JavaScript 클래스입니다. TextDecoder와 TextEncoder를 함께 사용하면 JavaScript에서 텍스트 데이터를 처리하는 간단하고 효율적인 방법을 제공하여 문자열과 문자 인코딩을 다루는 다양한 작업을 수행할 수 있게 해줍니다.

Node.js 18과 비교했을 때 디코딩 및 인코딩 속도가 상당히 향상되었습니다. UTF-8 파싱을 위해 simdutf를 추가하면 Node.js 16과 비교했을 때 디코딩 속도가 364% 향상되었습니다. 이는 매우 놀라운 개선입니다.

compare-util-16-18-bar.png

Node.js 20에서 이러한 개선 사항은 Node.js 18과 비교했을 때 성능 개선률이 25%로 더 좋아졌습니다. 자세한 결과는 state-of-nodejs-performance-2023 저장소에서 확인할 수 있습니다.

또한 Node.js 18에서 인코딩 메서드를 비교할 때도 성능 개선 사항이 관찰되었습니다. Node.js 16에서 Node.js 18로 이동할 때, TextEncoder.encodeInto 메서드의 성능이 현재 관측에서 93.67% 개선되었습니다(문자열 길이 256에 ascii를 사용):

compare-util-16-18-encode-bar.png

Node.js 벤치마크 작업

Node.js에서의 벤치마크 작업은 항상 제 호기심을 자극했습니다. Node.js와 그 하부 기술의 복잡성을 탐구하는 것을 즐기는 사람으로서, 특히 V8 엔진과 관련된 작업에 대한 자세한 내용을 알아보는 것은 매우 흥미로웠습니다. 실제로 저는 NearForm이라는 제가 소속된 회사를 통해 이 주제로 제 발견을 다른 사람들과 공유하는 것을 즐깁니다. 관련 프레젠테이션에 대한 자세한 정보는 이 링크를 클릭하면 확인할 수 있습니다.

또한 이러한 벤치마크에서는 1초 동안 수행된 작업 수를 나타내는 ops/sec 측정치를 사용할 것이며, 이는 컴퓨팅 시간의 아주 작은 부분만을 나타낼 수 있음을 강조해야 합니다. 이전 글(벤치마크 준비 및 평가)에서는 실제 응용 프로그램에서의 ops/sec에 대한 접근 방식에 대해 다루었으니 참고해보시기 바랍니다.

정수 파싱

문자열을 숫자로 파싱하는 것은 + 또는 parseInt(x, 10)를 사용하여 수행할 수 있습니다. 이전 벤치마크 결과에서는 이전 버전의 Node.js에서 +를 사용하는 것이 parseInt(x, 10)를 사용하는 것보다 빠르다는 것이 나타났습니다. 아래 표에서 확인할 수 있습니다:

이름ops/sec샘플
parseInt(x, 10) 사용 - 작은 숫자 (길이 2)283,768,53291
parseInt(x, 10) 사용 - 큰 숫자 (길이 10)21,307,115100
+ 사용 - 작은 숫자 (길이 2)849,906,952100
+ 사용 - 큰 숫자 (길이 10)849,173,33697

출처

그러나 Node.js 20과 새로운 V8 버전(11.4)이 출시되면서, 두 작업은 성능 측면에서 동등해졌습니다. 아래 업데이트된 벤치마크 결과에서 확인할 수 있습니다:

이름ops/sec샘플
parseInt(x, 10) 사용 - 작은 숫자 (길이 2)856,413,57598
parseInt(x, 10) 사용 - 큰 숫자 (길이 10)856,754,25996

| + 사용 - 작은 숫자 (길이

2) | 857,364,191 | 98 |
| + 사용 - 큰 숫자 (길이 10) | 857,511,971 | 96 |

출처

Super vs This

Node.js 20 추가와 관련된 흥미로운 벤치마크 중 하나는 클래스에서 this 또는 super를 사용하는 것입니다. 다음 예제에서 확인할 수 있습니다:

class Base {
  foo () {
    return 10 * 1e2
  }
}

class SuperClass extends Base {
  bar () {
    const tmp = 20 * 23
    return super.foo() + tmp
  }
}

class ThisClass extends Base {
  bar () {
    const tmp = 20 * 23
    return this.foo() + tmp
  }
}

Node.js 18에서 superthis를 비교했을 때, 다음과 같은 초당 작업 수(ops/sec)가 발생했습니다:

이름ops/sec샘플
super 사용159,426,60896
this 사용160,092,44091

출처

벤치마크 결과를 기반으로 보면, Node.js 20에서 this를 사용할 때 Node.js 18에 비해 성능이 크게 향상되었습니다. 이 향상은 상당히 놀랍다는 점에서 주목할 만하며, Node.js 20에서 853,619,840 ops/sec에 달하는 성과를 기록한 것을 알 수 있습니다. 이는 Node.js 18에서의 160,092,440 ops/sec에 비해 433% 개선된 결과입니다! 이로 보아 이제 this는 일반 객체의 속성 접근 방법과 동일한 속성 접근 방법을 갖춘 것으로 보입니다. 또한 두 작업 모두 동일한 전용 환경에서 테스트되었으므로 우연히 발생한 것은 아닙니다.

속성 접근

JavaScript에서 객체에 속성을 추가하는 여러 가지 방법이 있으며, 각 방법마다 그 목적과 때로는 모호한 특성이 있습니다. 개발자로서 각 방법에서 속성 접근의 효율성을 궁금해할 수 있습니다.

nodejs-bench-operations 저장소에는 이러한 방법들의 성능 특성을 밝히는 비교가 포함되어 있어 이러한 속성 접근에 대한 정보를 얻을 수 있습니다. 실제로 이 벤치마크 데이터는 Node.js 20에서 속성 접근이 크게 향상되었음을 보여줍니다, 특히 writable: trueenumerable/configurable: false 속성을 가진 객체를 사용할 때입니다.

const myObj = {};

Object.defineProperty(myObj, 'test', {
  writable: true,
  value: 'Hello',
  enumerable: false,
  configurable: false,
});

myObj.test // 속성 접근의 속도는 얼마나 빠를까요?

Node.js 18에서는 속성 접근(myObj.test)이 166,422,265 ops/sec를 생성했습니다. 그러나 동일한 상황에서 Node.js 20은 857,316,403 ops/sec을 생성하고 있습니다! 이와 관련된 특정 사항 및 속성 접근에 대한 자세한 내용은 다음 벤치마크 결과에서 찾을 수 있습니다:

  • 속성 getter 접근 v18 / v20
  • 속성 setter 접근 v18 / v20
  • 형태 변환 후 속성 접근 v18 / v20

Array.prototype.at

Array.prototype.at(-1)은 ECMAScript 2021 명세에서 도입된 메서드로, 배열의 마지막 요소에 접근할 때 배열의 길이를 알 필요 없이 음수 인덱스를 사용하지 않고도 사용할 수 있습니다. 이런 방식으로 at() 메서드는 array[array.length - 1]과 같은 전통적인 방법에 비해 배열의 마지막 요소에 더 간결하고 가독성 좋은 방법을 제공합니다.

Node.js 18에서는 이 접근이 Array[length-1]과 비교했을 때 상당히 느렸습니다:

이름ops/sec샘플
Length = 100 - Array.at26,652,68099
Length = 10,000 - Array.at26,317,56497
Length = 1,000,000 - Array.at27,187,82198
Length = 100 - Array[length - 1]

848,118,011 | 98 |
| Length = 10,000 - Array[length - 1] | 847,958,319 | 100 |
| Length = 1,000,000 - Array[length - 1] | 847,796,498 | 101 |

출처

Node.js 19부터, Array.prototype.at은 과거 방식인 Array[length-1]과 동일한 성능을 제공합니다. 아래 표는 이를 나타내고 있습니다:

이름ops/sec샘플
Length = 100 - Array.at852,980,77899
Length = 10,000 - Array.at854,299,27299
Length = 1,000,000 - Array.at853,374,69498
Length = 100 - Array[length - 1]854,589,19795
Length = 10,000 - Array[length - 1]856,122,24495
Length = 1,000,000 - Array[length - 1]856,557,97499

출처

String.prototype.includes

대부분의 사람들은 정규 표현식(RegExp)이 어떤 종류의 애플리케이션에서도 많은 병목 현상의 원인임을 잘 알고 있습니다. 예를 들어, 특정 변수가 application/json을 포함하고 있는지 확인하려고 할 수 있습니다. 그리고 이를 여러 가지 방법으로 수행할 수 있지만 대부분의 경우 다음 중 하나를 사용하게 될 것입니다:

  • /application\/json/.test(text) - 정규 표현식

또는

  • text.includes('application/json') - String.prototype.includes

몇몇 사람들이 알지 못하는 것 중 하나는 String.prototype.includes가 Node.js 16에서 정규 표현식과 거의 같은 속도로 동작한다는 것입니다.

이름ops/sec샘플
includes 사용16,056,20497
indexof 사용850,710,330100
RegExp.test 사용15,227,37098
캐시된 정규 표현식 패턴을 사용한 RegExp.test15,808,35097
new RegExp.test 사용4,945,47598
캐시된 정규 표현식 패턴을 사용한 new RegExp.test5,944,679100

원본

그러나 Node.js 18부터는 이 동작이 고친 것으로 나타났습니다.

이름ops/sec샘플
includes 사용856,127,951101
indexof 사용856,709,02398
RegExp.test 사용16,623,75698
캐시된 정규 표현식 패턴을 사용한 RegExp.test16,952,70199
new RegExp.test 사용4,704,35195
캐시된 정규 표현식 패턴을 사용한 new RegExp.test5,660,75595

원본

Crypto.verify

Node.js에서 crypto 모듈은 디지털 서명 생성 및 검증, 데이터 암호화 및 복호화, 안전한 난수 생성과 같은 다양한 목적으로 사용할 수 있는 암호화 기능 세트를 제공합니다. 이 모듈에서 사용 가능한 메서드 중 하나는 crypto.verify()입니다. 이 메서드는 crypto.sign() 메서드로 생성된 디지털 서명을 검증하는 데 사용됩니다.

Node.js 14 (End-of-Life)은 OpenSSL 1.x를 사용합니다. Node.js 16에서는 QUIC 프로토콜이 추가되었지만 여전히 OpenSSL 버전 1을 사용합니다. 그러나 Node.js 18에서는 OpenSSL을 버전 3.x로 업데이트하고 새로운 OpenSSL 버전 때문에 30k ops/sec에서 6~7k ops/sec로 감소하는 문제가 발견되었습니다. 저는 트윗에서 언급한대로 이 문제가 새로운 OpenSSL 버전 때문일 가능성이 높습니다. 다시 말씀드리지만, 저희 팀은 이 문제에 대해 조사 중이며 여러분이 이에 대한 어떤 통찰력이 있다면 이 이슈에 의견을 남겨 주시기 바랍니다: https://github.com/nodejs/performance/issues/72.

Node.js 성능 이니셔티브

Node.js 팀은 항상 API와 핵심 기능이 속도와 리소스 사용에 최적화되도록 주의를 기울여 왔습니다.

Node.js의 성능을 더 향상시키기 위해 팀은 최근에 '성능'이라는 새로운 전략 이니셔티브를 소개하였습니다. 이 이니셔티브는 Yagiz Nizipli가 의장을 맡고 있으며 Node.js 런타임과 핵심 모듈의 성능 병목 현상을 식별하고 해결하며 플랫폼의 전체 성능과 확장성을 향상시키는 것을 목표로 합니다.

성능 이니셔티브 외에도 현재 진행 중인 여러 다른 이니셔티브들이 있으며 Node.js의 다양한 측면을 최적화하는 데 중점을 둡니다. 이러한 이니셔티브 중 하나는 ‘Startup Snapshot’ 이니셔티브Joyee가 의장을 맡고 있습니다. 이 이니셔티브는 Node.js 애플리케이션의 시작 시간을 줄이는 데 중점을 둡니다. 이는 웹 애플리케이션의 전체 성능과 사용자 경험을 향상시키는 데 중요한 요소입니다

.

따라서 이 주제에 관심이 있다면 매주 다른 주에 개최되는 회의에 참여하고 OpenJS Foundation Slack#nodejs-core-performance 채널에서 메시지를 보내 주시기 바랍니다.

주목해야 할 사항

전략적 이니셔티브 외에도 현재 내가 이 글을 쓰는 시점에서 노드.js 성능에 큰 영향을 미칠 것으로 보이는 몇 가지 풀 리퀘스트가 있습니다:

Node.js에서 오류는 생성하기가 매우 비용이 듭니다. Node.js 애플리케이션에서 병목 현상의 주요 원인 중 하나입니다. 예를 들어, Node.js에서 fetch의 구현에 대한 연구를 수행하면 Node.js WebStreams 구현에서 오류 생성이 문제 중 하나임을 발견할 수 있습니다. 따라서 Node.js에서 오류 객체를 최적화함으로써 플랫폼의 전체 효율성을 향상시키고 병목 현상의 위험을 줄일 수 있습니다.

포인터 압축(Pointer compression)은 많은 포인터를 사용하는 프로그램의 메모리 사용량을 줄이기 위해 컴퓨터 프로그래밍에서 사용되는 기술입니다. 직접적으로 성능을 개선하지는 않지만 캐시 미스와 페이지 폴트를 줄이는 등의 방법으로 간접적으로 성능을 향상시킬 수 있습니다. 이것은 확실히 이슈 스레드에서 설명된대로 일부 인프라 비용을 줄일 수 있습니다.

2022년 3월에 이 문제가 생성되었으며, V8의 max_semi_space_size를 늘리는 것을 제안하여 Garbage Collection (특히 Scavenge) 실행을 줄이고 웹 툴링 벤치마크에서 전체 처리량을 늘릴 목적입니다. 이것은 아직 그 영향을 평가 중이며 Node.js 21에 포함되거나 포함되지 않을 수 있습니다.

이 PR은 Node.js 스트림의 기본 highWaterMark 값을 높입니다. 기본 옵션으로 Node.js 스트림 사용 시 성능 향상이 기대됩니다. 그러나 이 PR은 semver-major 변경이며 Node.js 21에 포함될 예정입니다. 자세한 벤치마크 결과를 보려면 연말에 'Node.js 성능 2023 - P2'를 기다리십시오.

결론

Node.js 20은 Node.js 이전 버전과 비교하여 성능이 크게 향상된 것으로 나타납니다. 자바스크립트 작업에서 주목할 만한 개선 사항은 속성 액세스, URL 구문 분석, 버퍼/텍스트 인코딩 및 디코딩, 시작/프로세스 라이프사이클 시간 및 EventTarget과 같은 영역에서 관찰되었습니다.

Node.js 성능 팀 (nodejs/performance)은 범위를 확장하여 각 새로운 버전마다 성능을 최적화하는 데 더 많은 기여를 하고 있습니다. 이 트렌드는 Node.js가 시간이 지남에 따라 계속해서 더 빨라질 것이라는 것을 나타냅니다.

벤치마크 테스트는 특정 작업에 중점을 두고 있으며 이러한 작업이 여러분의 비즈니스 요구 사항과 정확히 일치하는지를 반드시 검토하실 것을 권장합니다.

0개의 댓글