Галоўная / Артыкулы / TypeScript без Node чыра V8: Аналіз працэйсоў натывных бінарных файлаў scriptc

TypeScript без Node чыра V8: Аналіз працэйсоў натывных бінарных файлаў scriptc

Критэрыя старту, RSS, працёздатнасць і компромісы ў выкарыстоўванні CPU, калі той самы сервер HTTP на TypeScript запускаецца як натыўны бінарны файл ScriptC заместо Node.js з V8.

975 слоў

Запіскі парабанку, атрыманыя пасля запуску 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 перамераваўся у двух наладжэннях:

  1. Node.js v22.21.1, які запускаецца з вбудованым адключэнням типаў
  • scriptc v0.0.32 — стварае натыўны ELF x86-64 бінар
  • Нижэйшыя рэзультаты падсумавляюць тое, што змянілася пасля адключэння 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.