Состояние Node.js Performance в 2023 году

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

На дворе 2023 год, и мы выпустили Node.js v20. Это значительное достижение, и цель этой статьи — использовать научную оценку состояния производительности Node.js.

Все результаты бенчмарков содержат воспроизводимый пример и описание аппаратного обеспечения. Чтобы уменьшить количество шума для постоянных читателей, воспроизводимые шаги будут свернуты в начале всех разделов.

Цель этой статьи - предоставить сравнительный анализ различных версий Node.js. Она подчеркивает улучшения и недостатки, а также дает представление о причинах этих изменений, не проводя никаких сравнений с другими рантаймами JavaScript.

Для проведения эксперимента мы использовали Node.js версий 16.20.0, 18.16.0 и 20.0.0 и разделили сьюты бенчмарков на три отдельные группы:

  1. Node.js Internal Benchmark (внутренний бенчмарк)

Учитывая значительный размер и отнимающий много времени характер сьюта бенчмарка Node.js, я выбрал образцы, которые, по моему мнению, оказывают большее влияние на разработчиков и конфигурации Node.js, например, чтение файла размером 16 МБ с помощью fs.readfile. Эти бенчмарки сгруппированы по модулям, таким как fs и streams. Для получения дополнительной информации о сюьте бенчмарков 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 Internal Benchmark и nodejs-bench-operations, к обычным HTTP-приложениям.

Окружающая среда

Для выполнения этого бенчмарка использовался выделенный хост AWS со следующим оптимизированным для вычислений экземпляром:

  • c6i.xlarge (Ice Lake) 3,5 ГГц - оптимизированный для вычислений

  • 4 vCPU

  • 8 ГБ памяти

  • Canonical, Ubuntu, 22.04 LTS, amd64 jammy

  • 1 Гб SSD тип диска

Внутренний бенчмарк Node.js

В этом бенчмарке были выбраны следующие модули/пространства имен:

  • fs — файловая система Node.js

  • events — классы событий Node.js EventEmitter / EventTarget

  • http — Node.js HTTP сервер + парсер

  • misc — время запуска Node.js с использованием child_processes и worker_threads + trace_events

  • module — Node.js module.require

  • streams — создание, удаление, чтение потоков Node.js и многое другое

  • url — парсер URL Node.js

  • buffers — операции с буферами Node.js

  • util — Node.js кодировщик/декодировщик текста

Использованные конфигурации доступны по адресу RafaelGSS/node#state-of-nodejs, а все результаты были опубликованы в основном репозитории: State of Node.js Performance 2023.

Методика проведения бенчмарка Node.js

Прежде чем представить результаты, важно объяснить статистический подход, использованный для определения достоверности результатов бенчмарков. Этот метод был подробно описан в предыдущей статье блога, с которой вы можете ознакомиться здесь: Подготовка и оценка бенчмарков.

Чтобы сравнить влияние новой версии Node.js, мы запустили каждый бенчмарк несколько раз (30) на каждой конфигурации и на Node.js 16, 18 и 20. В таблице результатов есть две колонки, которые требуют пристального внимания:

  1. улучшение (improvement) процент улучшения по сравнению с новой версией

  2. достоверность (confidence) — сообщает нам, достаточно ли статистических данных для подтверждения улучшения.

Например, рассмотрим результаты следующей таблицы:

                                                                              confidence improvement accuracy (*)   (**)  (***)
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%

Be aware that when doing many comparisons the risk of a false-positive result increases.
In this case, there are 10 comparisons, you can thus expect the following amount of false-positive results:
  0.50 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.10 false positives, when considering a   1% risk acceptance (**, ***),
  0.01 false positives, when considering a 0.1% risk acceptance (***)

Существует риск в 0,1%, что fs.readfile не улучшился с Node.js 16 до Node.js 18 (достоверность ***). Следовательно, мы вполне уверены в результатах. Структуру таблицы можно представить следующим образом:

  • fs/readfile.js — бенчмарк файл

  • concurrent=1 len=16777216 encoding='ascii' duration=5 — опции бенчмарка. Каждый бенчмарк файл может иметь множество опций, в данном случае это чтение 1 параллельного файла с 16777216 байтами в течение 5 секунд с использованием ASCII в качестве метода кодировки.

Для любителей статистики, скрипт выполняет независимый/непарный 2-групповой t-тест с нулевой гипотезой, что производительность одинакова для обеих версий. Если p-значение меньше 0.05, в поле "Достоверность" появится звездочка. — Написание и запуск бенчмарков

Настройка бенчмарка

  1. Клонируйте репозиторий форка Node.js

  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 наблюдалось улучшение на 67% при использовании API fs.readfile с кодировкой ascii и на 12% при использовании utf-8.

Результаты бенчмарка показали, что при апгрейде Node.js с версии 16 до 18 улучшение API fs.readfile с кодировкой ascii составило около 67%, а при использовании utf-8 - около 12%. Файл, использованный для теста, был создан с помощью следующего сниппета:

const data = Buffer.alloc(16 * 1024 * 1024, 'x');
fs.writeFileSync(filename, data);

Однако при использовании fs.readfile с ascii на Node.js 20 наблюдалась регрессия в 27%. Об этой регрессии было сообщено команде Node.js Performance, и ожидается, что она будет исправлена. С другой стороны, fs.opendir, fs.realpath и fs.readdir показали улучшение от Node.js 18 до Node.js 20. Сравнение между Node.js 18 и 20 можно увидеть в приведенном ниже результате бенчмарка:

                                                                              confidence improvement accuracy (*)   (**)  (***)
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.10 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.02 false positives, when considering a   1% risk acceptance (**, ***),
  0.00 false positives, when considering a 0.1% risk acceptance (***)

Если вы используете Node.js 16, то можете ознакомиться следующим сравнением между Node.js 16 и Node.js 20

                                                                              confidence improvement accuracy (*)    (**)   (***)
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%

События

Класс EventTarget показал наиболее значительное улучшение в части событий. Бенчмарк включал диспетчеризацию миллиона событий с помощью EventTarget.prototype.dispatchEvent(new Event('foo')).

Апгрейд с Node.js 16 до Node.js 18 может обеспечить улучшение производительности диспетчеризации событий почти на 15%. Но настоящий скачок происходит при переходе с Node.js 18 на Node.js 20, который может дать улучшение производительности до 200% при наличии только одного слушателя.

Класс EventTarget является важнейшим компонентом Web API и используется в различных родительских фичах, таких как AbortSignal и worker_threads. В результате, оптимизация этого класса может потенциально повлиять на производительность этих фич, включая fetch и AbortController. Кроме того, API EventEmitter.prototype.emit также получил заметное улучшение примерно на 11,5% при сравнении Node.js 16 с Node.js 20. Полное сравнение приведено ниже для справки:

                                                                 confidence improvement accuracy (*)   (**)  (***)
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-сервер — это http.Server, который на каждый запрос отвечает 4 чанками по 256 байт каждый, содержащими 'C', как вы можете видеть в этом примере:

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%.

Данные результаты были получены с помощью бенчмарк-метода test-double-http. Это простой скрипт Node.js для отправки HTTP GET-запросов:

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, мы наблюдали значительное снижение заявленного улучшения — с 96% до 9%. Это указывает на то, что предыдущий метод бенчмаркинга имел ограничения или ошибки. Однако фактическая производительность HTTP-сервера улучшилась, и нам необходимо тщательно оценить процент улучшения с помощью нового бенчмаркингового подхода, чтобы точно измерить достигнутый прогресс.

Следует ли мне ожидать улучшения производительности на 96%/9% в моем приложении Express/Fastify?

Безусловно, нет. Фреймворки могут не использовать внутренний 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] (сценарий), где [mode] может быть:

  • process — новый процесс Node.js

  • worker — worker_thread (рабочий поток) Node.js.

А [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

Источник: https://habr.com/ru/companies/otus/articles/739270/


Интересные статьи

Интересные статьи

В самом начале года мы писали о том, что предсказанное аналитиками ранее снижение продаж ноутбуков и ПК отстает от реального положения вещей. И это несмотря на то, что эксперты были достаточно пес...
Хотите войти в веб-разработку, но не знаете, с чего начать? Освоение CSS станет отличным стартом и, к счастью для вас, в сети существует огромное количество учебных ресурсов. Мы провели исследовани...
AWS Lambda существует уже несколько лет, и она остается самым популярным способом экспериментировать с технологией serverless. Если вы не знакомы с serverless, то это модель разработки, в которой упра...
Objection.js — сравнительно молодая и минималистичная ORM-библиотека для Node.js, которая сильно упрощает взаимодействие с базами данных и не перегружена дополнительными функциями, как Sequelize или T...
IT-бизнес остаётся направлением с высокой маржинальностью, далёко опережая производство и некоторые другие виды услуг. Создав приложение, игру или сервис, можно работать не только на локальных, н...