TypeScript без Node чыра V8: Аналіз працэйсоў натывных бінарных файлаў scriptc
Критэрыя старту, RSS, працёздатнасць і компромісы ў выкарыстоўванні CPU, калі той самы сервер HTTP на TypeScript запускаецца як натыўны бінарны файл ScriptC заместо Node.js з V8.
Запіскі парабанку, атрыманыя пасля запуску HTTP-сэрвіса на TypeScript як натыўнага бінара, а не пад Node.js з V8.
JavaScript для бэкенду практычна завжды вымагае средовы для запуску. Пад Node, Deno чыста Bun такім средовым зазвычай являецца V8 ад Google: JIT-кампіляванне у машынны код, збір сметлівакоў і цыкл адбываюцыхся здач, налаштаваны для канкурэнціі.
Іншы эксперымент прабуе выявіць, чы можа сервер на TypeScript адмовіцца ад Node, V8 і ўсіх іншых двайкаў JavaScript, кампілюючы код у адны самастойны натыўны бінар.
Экспэрыментальны кампілятор Vercel Labs scriptc следуе гэтаму падходу. TypeScript перакладаецца у LLVM IR чыста проміжны C, а потым даканчваецца за дапамогою звычайных кампілятораў, такіх як clang.
Той самы HTTP-сэрвіс на TypeScript перамераваўся у двух наладжэннях:
- Node.js v22.21.1, які запускаецца з вбудованым адключэнням типаў
Нижэйшыя рэзультаты падсумавляюць тое, што змянілася пасля адключэння V8 з таго сервера.
1. Працовы навантажэнне для параболі
Была важліва справядлівасць, таму сервер выкарыстоўвае стандартны http модуль Node без дадатковых залежнасцей. Аўтарскі текст, бізнес-логіка і межы HTTP застаюцца ідэнтычнымі на обох хостах.
Адрабатваныя маршруты:
/health— дышэўны тэст гатовасці/json— структураваны JSON-тэкст/compute— цільвы числовы цикл/hash— SHA-256 за дапамоюnode:crypto
import { createServer } from "node:http";
import { createHash } from "node:crypto";
const server = createServer((req, res) => {
const url = new URL(req.url ?? "/", "http://localhost");
if (req.method === "GET" && url.pathname === "/health") {
res.writeHead(200, { "content-type": "application/json" });
return res.end(JSON.stringify({ status: "ok" }));
}
// Additional routes (/json, /compute, /hash)
});
server.listen(process.env.PORT || 3000);
2. Час запуску
Платформы без сервера, функцыі на краю сеті і контэйнеры, якія можаць зменшыцца да нуля, не аб’яднаюцца часам запуску процэса. Час виміроўвалі з моменту стварэння процэса пакуль /health не адпавядаў успешна, працэс павтарылі дзесяць разоў.
- Node.js пасредній час складаў прыблізна 232,16 мс
- scriptc пасредній час складаў прыблізна 14,23 мс (прыблізна у 16,3 раза быстрэй)
Адсутнасць запуску V8, виткоў на парсаванне і налаштаваннях звалікання типа дазволяе натыўнаму бінару ўзнясці адпаведь майже негараздо.
3. RSS у стане спакою і вяртуальны размер
V8 заздалегідь выкарыстоўвае памяць heap і буферы JIT. Скількі памяці залишаецца зарезерваванай, пакуль процэс чакае?
- Фізычны RSS для Node быў прыблізна 70,40 МБ
- Фізычны RSS для scriptc быў прыблізна 2,55 МБ (прыблізна у 27,6 раза менш)
Вяртуальныя размеры паказалі болей чыткую карціну: Node паказаў прыблізна 21,5 GB VSZ за счытам стратэгіі каратаўвання V8, тады як scriptc застаўся блізу 4,09 MB.
4. Пропускная спроможнасць і затрымка
oha ствараў навантажэнне прыблізна 10 секунд з 10, 100 і 500 адночаснымі кліентамі.
Лёгкі запыт (/health)
- Пры 500 адночасных кліентах scriptc даў прыблізна 29,828 RPS, тады як Node — 9,966 RPS (~2,99×).
- Пры 100 адночасных кліентах затрымка p50 для scriptc была прыблізна 2,78 ms, тады як для Node — 8,19 ms.
Адпаведзі ў формате JSON (/json)
З урахоўваннем серыявання scriptc досяг прыблізна 24,209 RPS, тады як Node — 10,012 RPS пры 100 адночасных кліентах (~2,42×).
5. Шляхі з CPU: ahead-of-time проты just-in-time
Шляхі, спрямованые на CPU, паказваюць, дзе розлічваюцца методы AOT і JIT.
Арыметычны цикл на /compute
Програма павтарае вырачунак (result + i * 31) % 1_000_000_007.
- Node адмаўляе прыблізна 2,835 RPS (p50 адпаведна 27,38 мс)
- scriptc адмаўляе прыблізна 2,620 RPS (p50 адпаведна 32,21 мс)
У гэтым случае JIT у Node перамагае прыблізна на 15%. Анаіз коду, выданага scriptc, паказвае, што локальныя зменныкі ўтвараюцца як C-значэнні типу double, а вырачунак модулявання выконваецца за дапамогою fmod():
double sc_t11 = fmod(sc_t9, sc_t10);
Выклік fmod() спрычыняе затраты дынамічнай бібліятэкі. V8 за час выконання фіксуе паведзенне, сэроднее цэлым числам, і можа адмаўляць вырачунакі як натыўныя дзеленні 32-бітных цэлах, што ў меру дешавае.
SHA-256 на /hash
Процес гэшавання адбываецца через node:crypto.
- Node: апоўна 9,873 RPS (p50 апоўна 8,45 мс)
- scriptc: апоўна 28,849 RPS (p50 апоўна 2,97 мс)
scriptc працюе прыблізна 2,9× быстрэй. Node два разы пераходзіць на C++ для .update() і .digest(), ў кожны раз платячы за дадатковыі трацытак OpenSSL. scriptc з’едынае весь процес у адній внутрашней вызов:
ScrStr *sc_t212 = scr_crypto_hash_digest_str(sc_t209, sc_t210, sc_t211);
Работа ў внутрашней памяці усунее неабходнасць пераводу дадзеных між JS і внутрашняй памяцю.
6. Размер адпраўкі контейнера
Пакаванне для размешчэння у стылі Kubernetes:
- Адпраўкі, заснованыя на
node:22-slim, маюць размер апоўна 120 MB - Адпраўкі, заснованыя на
debian:bookworm-slimі ўключаючы толькі дынамічна з’еднаны бінарны файл scriptc, маюць размер апоўна 80 MB; статычнае пакаванне можа дасягнуць 25 MB
7. Серьозныя меры обмежэння scriptc сёння
Кампайляр застаецца експерыментальным і з мяркуемым дзеянням:
Дынамічныя шаблоны: актывае выкарыстоўванне any або чыста дынамічнага JavaScript прыводзіць да неабходнасці включэння QuickJS (~620KB) і пазбавляе прыналежных перадоў швайнасці AOT.
Экасыстэма пакетаў: багато модуляў npm выкарыстоўваюць внутршні элементы Node або механізмы рэфлексіі, якія не можна скампайляваць статычна.
Абсэнца JIT: як паказаў /compute, спецыялізацыя типаў у час выканання за дапамогою JIT недаступна.
8. Практычныы вывод
Храніцеся на Node, калі:
- Неабходныя велікія графыкая залежнасцей npm
- Код мае дынамічны характер або є слаба типаваны
- Дапамога JIT у ціфровых циклах з дынамічным типаваннем мае значэнне
Разглядзіце scriptc, калі:
- Хосты, які працуюць у режыме «скэл-ту-зеро», патрабуюць часу запуску меншага за 15 мс і размеру холаднага RSS меншага за 3 МБ
- Сэрвіс пераважна частка часу абгружае натыўныя бібліятэкі (крыптаграфія, сецьяранне) у вачынку тонкага API
Файлы з результатамі тэстаў знаходзяцца ў гэтым репазітарыі GitHub.