Бан может в конечном итоге сделать Node.js и/или Deno неактуальными, но преждевременно говорить, что оба проекта мертвы.

Bun – это новый проект, который должен быть совместим с Node.js, но при этом значительно повысить производительность. Не прошло и месяца, как стал общедоступным, а люди утверждают, что проекты Node.js и Deno мертвы.

Что нужно, чтобы «убить» программную платформу? Например, люди до сих пор используют COBOL, а сколько было предсказаний о смерти Perl, PHP или Java?

Проект Bun делает большие заявления о том, что он будет совместим с платформой Node.js, обеспечивая при этом огромные преимущества в производительности. Если это правда, это может легко заставить многих инженеров-программистов отказаться от Node.js. Но на это уйдет несколько лет. Node.js находится в сильной зрелой позиции, и проекту Bun предстоит проделать большую работу, прежде чем он сможет полностью вытеснить Node.js.

Но что произойдет, когда Bun станет достаточно стабильным и зрелым, чтобы запускать сложные приложения, работающие в настоящее время на Node.js?

Что меня интересует, так это оценить, может ли Бан запускать мои приложения так же, как и Node.js. Я, конечно, не единственный человек с такими вопросами.

Эти утверждения привлекли внимание многих людей. Я видел на YouTube группу людей, которые запускали простые тесты производительности, а затем кричали о том, как они ДОКАЗЫВАЮТ, что Bun намного быстрее, чем Node.js.

Конечно, простые тесты производительности мало что доказывают. Как я скажу позже, ошибочно придавать большое значение простому тесту.

Это привело меня к попытке запустить тест сложного приложения на Bun. А именно, я разработал систему генератора статических веб-сайтов (AkashaCMS), которую я использую для создания нескольких веб-сайтов, включая techsparx.com и greentransportation.info. AkashaCMS достаточно сложна, чтобы обеспечить хороший тестовый сценарий. Если Бан сможет успешно запустить AkashaCMS с более высокой производительностью, это подтвердит заявления команды Бана. Идея была здравая, но результат был ошибочным.

Я написал статью, полагая, что мне удалось отрендерить свой сайт с помощью Bun (также на Medium), когда оказалось, что вместо этого я случайно запустил Node.js, и мой тест оказался недействительным.

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

В настоящее время ошибки и неполные функции в Bun не позволяют использовать его для запуска AkashaCMS. Но я смог выполнить часть этого с помощью Bun. Как мы увидим, это показывает прирост производительности в некоторых областях.

Что такое Bun по сравнению с Node.js

Node.js — это платформа для выполнения приложений JavaScript на стороне сервера. Он появился на сцене в 2009 году, сделав громкие заявления о том, что однопоточная архитектура, управляемая событиями, может обеспечить преимущества в производительности системы по сравнению с обычно сложными архитектурами на основе потоков.

Node.js — это JavaScript, работающий вне браузера. С 2009 года вокруг Node.js выросла большая экосистема инструментов и фреймворков, охватывающая все виды инструментов разработки программного обеспечения, фреймворки веб-приложений, уровни ORM баз данных и даже наборы инструментов приложений с графическим интерфейсом.

Бан похож на Node.js, но отличается. Если Node.js основан на движке V8 от Chrome и написан на C++, то Bun основан на JavaScriptCore от Safari и написан на Zig. Вы, вероятно, никогда не слышали о Zig до того, как услышали о Bun, и я тоже, но Zig утверждает, что имеет много преимуществ по сравнению с другими языками системного программирования, такими как C++. В противном случае Бан стремится выполнить вариант использования Node.js, который заключается в поддержке запуска современного JavaScript вне веб-браузера.

Это не первая попытка запустить Node.js на другом движке JavaScript. Несколько лет назад была попытка запустить Node.js поверх ChakraCore (GitHub), от которой отказались, когда Microsoft удалила ChakraCore из Edge.

Преимущество булочки в следующем:

  1. Совместимость с Node.js, включая прямое использование пакетов (даже пакетов собственного кода) для Node.js.
  2. Огромные преимущества в производительности: а) Он написан на Zig, а не на C++, что имеет некоторые преимущества; б) Он построен на движке JavaScriptCore. Этот движок должен быть быстрее, чем движок V8, лежащий в основе Node.js и Deno.
  3. Он поддерживает прямое выполнение кода TypeScript и JSX.

Если команда Bun сможет полностью реализовать эти преимущества, я полагаю, что многие в сообществе Node.js/Deno перейдут на Bun.

Но с практической точки зрения Node.js 12 лет дорабатывался, улучшался и исправлялся. Бану, как мы увидим, предстоит многое наверстать.

Любой опытный инженер-программист, вероятно, использовал в своей карьере множество различных инструментов и платформ программирования. Мы постоянно оцениваем, какие инструменты использовать, и большинство из нас достаточно мудры, чтобы видеть заявления, сделанные командой Bun, и знать, что они делают очень большие заявления. Я очень надеюсь, что эти претензии оправдаются, но на данном этапе

Бан может убить Node.js и/или Deno

Уже есть люди, утверждающие, что Бан убьет и проекты Deno, и Node.js. Обоснование заключается в том, что я только что назвал: если Bun сможет реализовать все Node.js с высокой степенью совместимости, сохраняя при этом огромные преимущества в производительности, то у них явно есть победитель.

Поскольку Bun использует инфраструктуру node_modules, у него есть большое преимущество перед Deno. Deno с трудом использует сотни тысяч пакетов, доступных таким образом. Это ценный ресурс, который мы создали вместе, и Бан сможет в полной мере использовать его.

Например, можно использовать npm install для настройки каталога node_modules, а затем сразу использовать его с Bun. Цель состоит в том, чтобы использовать bun install с тем же package.json, чтобы делать то же самое, но с большей производительностью.

Бан не сможет сразу убить Node.js. Как мы видим ниже, есть много недостающих функций и много ошибок, которые нужно исправить. И есть все процессы и материально-техническое обеспечение, которые должны быть разработаны, чтобы Bun стал самоокупаемым проектом.

Что разработчики Node.js будут делать с альтернативой, совместимой с существующей экосистемой, но намного более быстрой?

Ловушка простых тестов производительности

На YouTube уже есть несколько видеороликов, в которых Бан впервые пытается это сделать. Каждое видео, которое я смотрел, показывает, как они выполняют несколько простых команд и говорят, черт возьми, это так быстро.

Существует известная ошибка чрезмерно упрощенного теста производительности. Означает ли выполнение простого скрипта с помощью Bun, что он намного быстрее, чем Node.js, в реальных приложениях? Это заблуждение. Чтобы убедиться, что Bun действительно быстрее, требуется более глубокое тестирование, чем несколько простых примеров.

Существующие тесты производительности Бана

Исходное дерево Bun включает в себя набор эталонных тестов. Чтобы запустить эти тесты:

$ git clone https://github.com/oven-sh/bun.git 
$ cd bun/bench
$ bun install
$ bun run ffi
$ bun run log
$ bun run gzip
$ bun run async
$ bun run sqlite

У меня нет места, чтобы показать полный набор результатов. Но давайте посмотрим на тесты SQLite:

david@davidpc:~/Projects/bun/bun/bench/sqlite$ bun run bench
$ bun run bench:bun && bun run bench:node && bun run bench:deno
$ $BUN bun.js
[0.02ms] ".env"
cpu: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
runtime: bun 0.1.4 (x64-linux)

benchmark                        time (avg)             (min … max)
-------------------------------------------------------------------
SELECT * FROM "Order"         43.62 ms/iter   (40.67 ms … 47.89 ms)
SELECT * FROM "Product"      121.84 µs/iter  (87.83 µs … 928.85 µs)
SELECT * FROM "OrderDetail"  499.15 ms/iter  (470.1 ms … 620.22 ms)
$ $NODE node.mjs
cpu: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
runtime: node v18.6.0 (x64-linux)

benchmark                        time (avg)             (min … max)
-------------------------------------------------------------------
SELECT * FROM "Order"        108.33 ms/iter (106.17 ms … 113.98 ms)
SELECT * FROM "Product"       318.2 µs/iter (285.53 µs … 775.32 µs)
SELECT * FROM "OrderDetail"     2.13 s/iter       (2.02 s … 2.37 s)
$ $DENO run -A --unstable deno.js
cpu: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
runtime: deno 1.23.4 (x86_64-unknown-linux-gnu)

benchmark                        time (avg)             (min … max)
-------------------------------------------------------------------
SELECT * FROM "Order"         274.7 ms/iter (263.29 ms … 342.62 ms)
SELECT * FROM "Product"      490.34 µs/iter   (377.47 µs … 7.49 ms)
SELECT * FROM "OrderDetail"      1.6 s/iter       (1.43 s … 2.12 s)

Этот конкретный тест выполняет SELECT запросов к базе данных SQLite. Для Node.js в тесте используется better-sqlite, а в тесте Deno — "https://deno.land/x/sqlite/mod.ts". В отличие от этого, Bun использует собственную реализацию SQLite, напрямую интегрированную в исходные коды Bun.

Это впечатляющие различия в производительности.

Неполноты в Bun, препятствующие более глубокому тестированию

Моя цель — запускать более крупные приложения для оценки совместимости и производительности Bun. В моем случае этим приложением является AkashaCMS, генератор статических веб-сайтов, который я использую для techsparx.com, greentransportation.info и пары других веб-сайтов. AkashaCMS выполняет обработку DOM на стороне сервера с помощью Cheerio, который использует различные механизмы шаблонов (в первую очередь EJS и Nunjucks) и многое другое. Другими словами, это послужит хорошей проверкой совместимости Bun с Node.js.

Но есть много проблем с совместимостью:

$ bun ./node_modules/akasharender/cli.js copy-assets config.js
error: Cannot find package "child_process" from "/home/david/ws/techsparx.com/node_modules/akasharender/node_modules/commander/index.js"

Команда akasharender использует Commander для разбора аргументов и не может работать, потому что пакет child_process не существует. Commander — чрезвычайно популярный пакет для написания инструментов CLI в Node.js. Отсутствие встроенного пакета child_process препятствует запуску любого инструмента, созданного для Commander.

FWIW, очередь задач Bun содержит Дорожную карту Buns, в которой перечислено множество вещей, которые не были реализованы.

Я надеялся просто использовать AkashaCMS для оценки Бана. Поскольку это невозможно сделать, следующим лучшим решением будет выбор частей AkashaCMS для проведения оценки.

Обработка текста с помощью шаблонизаторов, производительность и совместимость между Bun и Node.js

Обсуждаемый здесь код находится в: https://github.com/akashacms/akashacms-perftest/tree/master/bench.

akashacms/akashacms-perftest используется для тестирования производительности AkashaCMS. В каталоге bench я намереваюсь создать тесты, подобные эталонным, для некоторых функций AkashaCMS.

Например, рассмотрим:

import { bench, run } from "mitata";

let people = ['geddy', 'neil', 'alex'];

// TEMPLATE STRINGS

bench('literal', () => { return `${people.join(', ')}`; });

// EJS

import * as ejs from 'ejs';

bench('ejs-join', () => {
    ejs.render('<%= people.join(", "); %>', { people: people });
});
bench('ejs-list', () => {
    ejs.render(`
    <ul>
    <% people.forEach(function (person) {
        %><li><%= person %></li><%
    }) %>
    </ul>
`, { people: people });
});

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

Первый тест — проверка подстановки текста в строке шаблона.

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

Два дополнительных теста:

  • markdown-render, который использует пакет MarkdownIT для обработки Markdown.
  • cheerio для обработки DOM на стороне сервера с использованием cheerio, основной функции AkashaCMS.

Результат показывает следующие различия в производительности:

$ npm run bench

> [email protected] bench
> npm-run-all render:node render:bun

> [email protected] render:node
> node render-node.mjs

cpu: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
runtime: node v18.6.0 (x64-linux)

benchmark            time (avg)             (min … max)
-------------------------------------------------------
literal          133.73 ns/iter (119.76 ns … 661.32 ns)
ejs-join          18.15 µs/iter  (14.89 µs … 583.95 µs)
ejs-list           30.6 µs/iter  (25.98 µs … 398.45 µs)
handlebars-join    5.95 µs/iter   (4.78 µs … 371.31 µs)
handlebars-list    5.97 µs/iter   (4.76 µs … 411.31 µs)
liquid-join       30.26 µs/iter    (18.04 µs … 3.66 ms)
liquid-list       91.91 µs/iter     (64.02 µs … 1.3 ms)
nunjucks-join     46.25 µs/iter    (26.71 µs … 1.09 ms)
nunjucks-list     93.28 µs/iter    (65.62 µs … 1.22 ms)
markdown-render   38.78 µs/iter  (28.93 µs … 603.53 µs)
cheerio          130.17 µs/iter    (78.42 µs … 5.04 ms)

> [email protected] render:bun
> bun render-bun.js

cpu: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz
runtime: bun 0.1.4 (x64-linux)

benchmark                 time (avg)             (min … max)
------------------------------------------------------------
literal               129.64 ns/iter (107.87 ns … 534.11 ns)
handlebars-join-once    4.34 µs/iter     (3.07 µs … 1.17 ms)
handlebars-list-once    4.66 µs/iter     (3.44 µs … 1.22 ms)
liquid-join            38.63 µs/iter    (21.23 µs … 2.63 ms)
liquid-list           125.84 µs/iter    (87.41 µs … 2.12 ms)
cheerio                68.81 µs/iter    (43.25 µs … 2.09 ms) 

Из этого можно извлечь две вещи:

  1. Я не смог реализовать все сценарии и на Node.js, и на Bun.
  2. В тесте Cheerio наблюдается значительный прирост производительности, а в остальных менее значительный.

Почему нельзя было реализовать полный набор сценариев на обоих? Были ошибки сегментации для определенных сценариев в Mitata. В очередь задач Bun добавлена ​​проблема с описанием проблемы: https://github.com/oven-sh/bun/issues/811.

Для некоторых механизмов шаблонов комбинация Mitata и этого механизма шаблонов вызывала ошибку сегментации. Скрипт, работающий только с механизмом шаблонов, без корректно выполненного кода Mitata.

Что касается производительности, Бан показывает прирост производительности для сценариев, которые работают на обеих платформах. Прирост производительности Cheerio очень интересен.

Чокидар раскрывает проблемы

Chokidar — популярный пакет для сканирования деревьев каталогов и динамического обнаружения изменений. В AkashaCMS он используется для обнаружения изменений файлов и автоматического восстановления. Он играет основную роль, и я хотел знать, есть ли разница во времени выполнения для сканирования данного каталога между Node.js и Bun.

Тесты для этого здесь: https://github.com/akashacms/stacked-directories/tree/master/example/adhoc

Тестовый случай таков:

import { inspect } from 'util';
import { default as chokidar } from 'chokidar';

let watcher;

const start = new Date();
let count = 0;

try {
    await new Promise((resolve, reject) => {
        try {
            watcher = chokidar.watch(process.argv[2]);
            watcher
            .on('error', async (error) => {
                console.error(error);
                reject(error);
            })
            .on('add', (fpath, stats) => {
                // console.log(`add ${fpath} ${inspect(stats)}`);
                count++;
            })
            .on('change', (fpath, stats) => {
                // console.log(`change ${fpath} ${inspect(stats)}`);
            })
            .on('ready', async () => {
                // console.log(`ready`);
                await close();

                const finish = new Date();

                console.log(`time ${(finish - start) / 1000} seconds - ${count} files`);

                resolve();
            });
        } catch (err) { reject(err); }
    });

} catch (errr) { console.error(errr); }

async function close() {
    await watcher.close();
    watcher = undefined;
}

Это использует Chokidar для сканирования каталога, переданного в командной строке. Для моего теста я просканировал каталог node_modules, чтобы убедиться, что там много файлов для сканирования. Chokidar выдает несколько событий в зависимости от того, что происходит в файловых системах, которые он сканирует. Событие ready генерируется после завершения начального сканирования. Мы используем это событие, чтобы закрыть экземпляр Chokidar и рассчитать время.

Это означает, что он может служить для сравнения производительности между Node.js и Bun, но, как мы увидим, Bun не может выполнить Chokidar.

С Node.js:

$ node choke.mjs ../../node_modules/
time 0.763 seconds - 3498 files

Но с Буном тест не проходит:

$ bun choke.mjs ../../node_modules/ 
241 |     ); 
242 |   } 
243 |  
244 |   filterPath(entry) { 
245 |     const {stats} = entry; 
246 |     if (stats && stats.isSymbolicLink()) return this.filterDir(entry);
                      ^  TypeError: stats.isSymbolicLink is not a function. (In 'stats.isSymbolicLink()', 'stats.isSymbolicLink' is undefined)
       at filterPath (/home/david/Projects/akasharender/stacked-directories/node_modules/chokidar/index.js:246:17)
       at /home/david/Projects/akasharender/stacked-directories/node_modules/readdirp/index.js:141:79

Это означает, что объект Stats, возвращаемый fs.stats, не содержит функцию isSymbolicLink. Эта функция существует с Node.js v10.10.0 и, безусловно, должна быть там.

Проблема уже исправлена ​​— мой отчет об ошибке https://github.com/oven-sh/bun/issues/797 — обратите внимание, что внизу есть ссылка на отслеживание ошибок реализации дополнительных методов объекта Stats. На момент написания этой статьи выпущен Bun 0.1.5, и теперь существует функция isSymbolicLink.

К сожалению, с Chokidar произошла новая ошибка.

114 |         sysPath.resolve(path, evPath), KEY_LISTENERS, sysPath.join(path, evPath) 
115 |       ); 
116 |     } 
117 |   }; 
118 |   try { 
119 |     return fs.watch(path, options, handleEvent);
                ^ TypeError: fs.watch is not a function. (In 'fs.watch(path, options, handleEvent)', 'fs.watch' is undefined)
       at createFsWatchInstance (/home/david/Projects/akasharender/stacked-directories/node_modules/chokidar/lib/nodefs-handler.js:119:11)
       at /home/david/Projects/akasharender/stacked-directories/node_modules/chokidar/lib/nodefs-handler.js:166:14
       at _watchWithNodeFs (/home/david/Projects/akasharender/stacked-directories/node_modules/chokidar/lib/nodefs-handler.js:331:13)
       at _handleFile (/home/david/Projects/akasharender/stacked-directories/node_modules/chokidar/lib/nodefs-handler.js:395:17)
       at /home/david/Projects/akasharender/stacked-directories/node_modules/chokidar/lib/nodefs-handler.js:637:15

Действительно, мы можем убедиться, что он не существует как таковой:

import * as fs from 'fs';
console.log(fs.watch);

В Node.js это печатает объект Function, но в Bun в настоящее время печатается undefined. Проблема зарегистрирована: https://github.com/oven-sh/bun/issues/832

Краткое содержание

Одна из булочек в духовке (по приоритетному списку) описывается так:

Улучшение совместимости с Node.js. Экспресс должен работать. Популярные пакеты вроде chalk, debug, discord.js должны работать. child_process и net должны быть реализованы. Нам нужно больше тестов. Эти пакеты должны поддерживаться за счет реализации низкоуровневых API-интерфейсов Node.js, а не за счет взлома слоев совместимости. В долгосрочной перспективе Бан может захотеть реализовать Express в нативном коде, но сегодня давайте просто наладим работу примитивов.

И да, приведенное выше ясно демонстрирует, что в настоящее время отсутствует совместимость с Node.js. Но это всего лишь вопрос исправления ошибок или добавления недостающих функций. Как сказано в списке приоритетов, увеличение количества людей, работающих над проектом, поможет решить эти проблемы.

Если проект Bun позволит мне предложить пару вещей:

  • Объективный и точный список тестов совместимости. Иными словами, было бы неплохо иметь список основных модулей Node.js и текущий статус реализации. Для вдохновения загляните на https://node.green/
  • Измерения производительности должны отслеживаться в базе данных, чтобы мы могли наблюдать улучшения с течением времени.
  • Измерений производительности должно быть гораздо больше, чем сейчас. (Мне следует подумать о сценариях, которые я описал выше)

Первое предложение, тест на совместимость API Node.js, исходит из моего более чем 10-летнего опыта работы в команде Java SE в Sun Microsystems. (См. мою статью о моем переходе с Java на Node.js.) Существует несколько реализаций Java, созданных разными организациями для разных платформ. Совместимость между несколькими реализациями Java гарантируется соответствующими TCK. Я не помню, что означает эта аббревиатура, но это тест на совместимость, а точнее на соответствие спецификации.

Команда Bun собирается создать вторую реализацию Node.js API. Это ставит его в ту же роль, что и независимо разработанные реализации Java.

Нам, как потенциальным клиентам Node.js или Bun, необходимо знать степень совместимости между ними. Наша работа может состоять в том, чтобы поддерживать веб-сайт многомиллиардного бизнеса, и неправильный выбор между ними может убить бизнес.

В экосистеме Java тесты на соответствие/совместимость были большим шагом к установлению доверия. Прохождение этих тестов имело большое значение и позволяло проекту или продукту называть себя Java-совместимым.

Для Node.js такого набора тестов не существует. Также не существует формальной спецификации API Node.js. Документация по API на https://nodejs.org/api/index.html хороша, но это не формальная спецификация. В экосистеме Java тесты на соответствие пишутся путем тщательного разбора деталей спецификации.

Со временем могут быть и другие попытки создать независимые реализации Node.js API. Как и команда Bun сегодня, эти команды столкнутся с той же проблемой определения степени совместимости с Node.js.

Теоретически от команды требуется изучить документацию API Node.js. Тесты на соответствие можно было бы разработать, в идеале, как независимый проект, который могли бы использовать как команды Node.js, так и команды Bun.

Однако для этого потребуется много работы, и у кого есть средства для оплаты такого набора тестов?

В нынешнем виде команда Bun создаст любые тесты, которые, по их мнению, подходят для Bun. Если бы я работал в команде Bun, я бы искал способ использовать тесты, разработанные командой Node.js. Я мог бы даже сделать это в отдельном репозитории. Цель состоит в том, чтобы команды Bun и Node.js совместно работали над набором тестов API Node.js.

На ум пришла идея: кто-то мог бы разработать веб-сайт или репозиторий GitHub, через который владельцы проектов могли бы объявить «Works With Bun»… Растущий список «Works With Bun» пакеты должны помочь доказать, что Bun — хороший выбор. По каким критериям владелец инициативы «Works With Bun» будет проверять ее достоверность?

Может быть, я слишком отвлекся.

Эта статья доказала две вещи:

  1. Слишком рано пытаться запускать важные приложения на Bun из-за многих недостающих функций.
  2. Прирост производительности зависит от функции.

Суть в том, что Bun добьется успеха, если примет участие сообщество Node.js. Предстоит проделать большую работу, прежде чем можно будет рекомендовать использовать Bun для важных приложений. Прирост производительности очень многообещающий.

об авторе

Дэвид Херрон: Дэвид Херрон — писатель и инженер-программист, занимающийся вопросами разумного использования технологий. Его особенно интересуют экологически чистые энергетические технологии, такие как солнечная энергия, энергия ветра и электромобили. Дэвид почти 30 лет работал в Силиконовой долине над программным обеспечением, начиная от систем электронной почты и заканчивая потоковым видео и языком программирования Java, и опубликовал несколько книг о программировании Node.js и электромобилях.

Первоначально опубликовано на https://techsparx.com.