Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Parsing: kodni o'qib chiqish
- 2.1 Avval butun fayl tekshiriladi
- 2.2 Tokenlar va AST
- 2.3 Dangasa tahlil
- 3. Ignition: bytecode va interpretator
- 3.1 Bytecode nima
- 3.2 [0] — kuzatuv daftari
- 4. JIT: issiq kodni tezlashtirish
- 4.1 To'rt bosqich
- 4.2 Har bosqich qanchalik tez
- 5. Optimallashtirishni ko'rish: --trace-opt
- 5.1 Funksiya qanday "ko'tariladi"
- 5.2 Har safar bir xil emas
- 6. Deoptimizatsiya
- 6.1 Taxmin buzilganda
- 6.2 Deopt qachon zarar
- 6.3 Butun yo'l bitta jadvalda
- 7. Ko'p uchraydigan xatolar
- 7.1 "Dvigatelni aldash" uchun kodni buzish
- 7.2 Bayroqlarni real dasturda ishlatish
- 7.3 Trace chiqishini aniq raqam deb o'qish
- 7.4 SyntaxError ni bajarilish xatosi deb o'ylash
- 8. Mashqlar
- 1-mashq (oson): Bytecode'ni o'qing
- 2-mashq (o'rta): Deoptni o'zingiz chaqiring
- 3-mashq (qiyin): Bosqichlarni o'lchang
- 9. Real ishda
- Xulosa
- Manbalar
JS dvigateli ichida: parsing, AST, Ignition bytecode, JIT va deoptimizatsiya
Qisqacha: JavaScript dvigateli (Chrome va Node'da — V8) kodni avval tahlil qiladi (parsing) va daraxt (AST) yasaydi. Daraxtdan bytecode chiqadi — uni Ignition interpretatori qatorma-qator bajaradi. Ko'p chaqiriladigan "issiq" funksiyalarni V8 ish davomida mashina kodiga kompilyatsiya qiladi (JIT): Sparkplug, Maglev, TurboFan. Agar funksiyaga kutilmagan turdagi qiymat kelsa, tez kod tashlab yuboriladi — bu deoptimizatsiya.
Bu darsda
- Kod ishga tushishidan oldin nima bo'lishini (parsing, AST) tushuntira olasiz va nega sintaksis xatosi bitta qatorni ham bajartirmasligini bilasiz.
node --print-bytecodebilan funksiyaning bytecode'ini ko'rib, uni o'qiy olasiz.- V8 ning to'rt bosqichini (Ignition, Sparkplug, Maglev, TurboFan) va ularning tezlik farqini o'lchangan raqamlar bilan tushuntira olasiz.
--trace-opt --trace-deoptchiqishini o'qib, funksiya qachon optimallashganini va nega deoptimizatsiya bo'lganini topa olasiz.
Oldin bilishingiz kerak: class ichidan: prototip ustidagi "shakar", Event loop, Memoization.
1. Nega bu kerak?
Oldingi darsda — Xato va bo'sh qiymatlar bilan FP uslubida ishlash — funksional uslub bo'limini tugatdik. Endi kodning ichiga, dvigatelga tushamiz.
Siz bir necha yuz dars davomida JavaScript yozdingiz va node fayl.js bilan ishga tushirdingiz. Lekin kompyuter protsessori JavaScript'ni bilmaydi. U faqat o'z buyruqlarini — mashina kodini tushunadi. Demak, orada kimdir narx * soni ni protsessor tiliga o'girib beradi. Bu "kimdir" — JS dvigateli (engine). Chrome va Node'da u V8 deb ataladi, Firefox'da — SpiderMonkey, Safari'da — JavaScriptCore.
Dvigatelni bilmasdan ham kod yozsa bo'ladi. Lekin uch holatda u albatta kerak bo'ladi:
- Kod sekin ishlasa — nega sekinligini tushunish uchun.
- Oldingi darslardagi va'dalar: Class maydonlari darsida "bir xil tuzilishdagi obyektlar tezroq" degan edik, Memoization darsida esa "dvigatel kodni qizdiradi" degan edik. Bugun bu gaplarning ichini ochamiz.
- Intervyu: "V8 qanday ishlaydi?", "JIT nima?" — o'rta darajadagi frontend intervyularida tez-tez so'raladi.
«Bahor» oshxonasidan o'xshatish. Oshpaz Rustam aka yangi retseptni oladi. Avval uni boshidan oxirigacha o'qib chiqadi — xato yo'qmi, tushunarlimi. Keyin uni qisqa qadamlar kartochkasiga ko'chiradi: "guruch yuv, sabzi to'gra, qozonni qizdir". Birinchi kunlari kartochkaga qarab pishiradi — sekin, lekin ishonchli. Osh har kuni yuzlab buyurtma qilinsa, Rustam aka retseptni yod oladi va kartochkasiz, tez pishiradi. Bir kuni mehmon "oshni guruchsiz, grechka bilan" desa — yod olgan usul ishlamaydi. Rustam aka yana kartochkaga qaytadi.
Dvigatel ham xuddi shunday ishlaydi. Shu o'xshatishni dars davomida ishlatamiz.
2. Parsing: kodni o'qib chiqish
2.1 Avval butun fayl tekshiriladi
Kichik tajriba. Quyidagi faylda birinchi qator to'g'ri, xato esa hech qachon chaqirilmaydigan funksiya ichida:
console.log("Bahor ochildi");
function hechChaqirilmaydi() {
let narx = ;
}Konsolda:
SyntaxError: Unexpected token ';'Unexpected token ';' — "kutilmagan belgi ;". Dvigatel = dan keyin qiymat kutgan, lekin nuqtali vergul ko'rgan. E'tibor bering: "Bahor ochildi" chiqmadi. Birinchi qator mutlaqo to'g'ri bo'lsa ham. Funksiya esa umuman chaqirilmaydi.
Sabab: dvigatel kodni bajarishdan oldin uni butunlay tahlil qiladi. Tahlil (parsing) — matnni o'qib, uning tuzilishini aniqlash. Matnda grammatika xatosi bo'lsa — hech narsa bajarilmaydi. Rustam aka ham retseptning oxirgi sahifasi yirtilganini ko'rsa, birinchi qadamni boshlamaydi.
Maslahat: Shu sababli
SyntaxErrordoim dastur boshida chiqadi,TypeErroresa bajarilish o'rtasida. Birinchisi — "o'qib bo'lmadi", ikkinchisi — "o'qildi, lekin bajarishda muammo chiqdi".
2.2 Tokenlar va AST
Tahlil ikki qadamda o'tadi. Rustam aka ham retseptni shunday o'qiydi. Avval matnni alohida so'zlarga ajratadi: "guruch", "1 kg", "yuv". Keyin qaysi so'z qaysi qadamga tegishli ekanini tushunadi.
Dvigatelda birinchi qadamni skaner (scanner) qiladi. U matnni tokenlarga — ma'noli bo'laklarga — ajratadi: const, jami, =, narx, *, soni, ;. Bo'sh joylar va izohlar shu yerda tashlab yuboriladi.
Ikkinchi qadamni parser ("tahlilchi") qiladi: u tokenlardan daraxt quradi. Bu daraxt AST (Abstract Syntax Tree, abstrakt sintaksis daraxti) deyiladi — kodning tuzilishi, "nima nimaning ichida" ekani. const jami = narx * soni; qatori uchun daraxt shunday ko'rinadi:
flowchart TD
A["VariableDeclaration (const)"] --> B["VariableDeclarator"]
B --> C["id: Identifier «jami»"]
B --> D["init: BinaryExpression «*»"]
D --> E["left: Identifier «narx»"]
D --> F["right: Identifier «soni»"]Daraxtni yuqoridan pastga o'qing: "bu — const e'loni; uning nomi jami; qiymati — ko'paytirish; ko'paytirishning chap tomoni narx, o'ng tomoni soni". Diagrammadagi nomlar — o'ylab topilmagan. Bu acorn 8.18.0 tahlilchisining haqiqiy chiqishi (u ESLint va ko'p vositalar ichida ishlaydi). V8 ning o'z AST'i ichki va biroz boshqacha, lekin g'oya bir xil. O'zingiz ham ko'rishingiz mumkin: astexplorer.net saytiga istalgan kodni yozing — o'ng tomonda daraxti chiqadi.
AST faqat dvigatelga kerak emas. Kodni o'qiydigan hamma vosita AST bilan ishlaydi: Prettier (kodni chiroyli qiladi), ESLint (xatolarni topadi), bundler'lar (ko'p faylni saytga tayyorlab yig'adigan vositalar — 16-qismda, hozir bilish shart emas). Birinchi ikkitasini ESLint va Prettier darsida ishlatamiz.
2.3 Dangasa tahlil
Katta saytda minglab funksiya bor, lekin sahifa ochilganda ularning ozi chaqiriladi. Hammasini to'liq tahlil qilish vaqtni behuda ketkazadi. Shuning uchun V8 dangasa tahlil (lazy parsing) qiladi: chaqirilmagan funksiyani faqat tez "oldindan tahlil" qiladi — sintaksisi to'g'rimi, tashqi o'zgaruvchilardan nimani ishlatadi. To'liq tahlil — funksiya birinchi marta chaqirilganda (V8 blogi, "Blazingly fast parsing, part 2: lazy parsing", 2019-04-15).
Oshxonada bu shunday ko'rinadi. Rustam akaning qalin retseptlar kitobida 300 ta taom bor. U har ertalab hammasini sinchiklab o'qimaydi. Faqat varaqlab chiqadi: "sahifalar joyida, yirtilgani yo'q". Sinchiklab o'qish — shu taomga buyurtma kelgandagina.
Yuqoridagi tajriba shuni ham ko'rsatdi: oldindan tahlil ham grammatikani tekshiradi. Shuning uchun chaqirilmagan funksiya ichidagi xato ham butun faylni to'xtatdi.
Tekshirib ko'ring: Faylning 500-qatorida
if (narx > 0 {(yopuvchi qavs yo'q) bor. 1-qatordagiconsole.logishlaydimi?
Javob
Yo'q. Bu sintaksis xatosi — fayl tahlil bosqichidan o'tmaydi, demak bajarilish umuman boshlanmaydi. Konsolda faqat SyntaxError chiqadi.
3. Ignition: bytecode va interpretator
3.1 Bytecode nima
AST — daraxt. Daraxtni to'g'ridan-to'g'ri bajarish noqulay. Shuning uchun V8 uni bytecode ga aylantiradi. Bytecode — dvigatel uchun yozilgan qisqa, oddiy buyruqlar ro'yxati. Bu — Rustam akaning "qadamlar kartochkasi". Ignition — shu buyruqlarni birma-bir bajaradigan interpretator (interpreter) — "kartochkaga qarab pishiruvchi". Ignition V8 ga 2016–2017 yillarda qo'shilgan (V8 blogi, "Launching Ignition and TurboFan", 2017-05-15).
Bytecode'ni o'z ko'zingiz bilan ko'rish mumkin. jami.js faylini yarating:
// jami.js — bytecode'ni ko'rish uchun
function jami(narx, soni) {
return narx * soni;
}
console.log(jami(35000, 2)); // 70000Va Node'ni maxsus bayroq bilan ishga tushiring. Bayroq (flag) — buyruqqa qo'shiladigan, -- bilan boshlanadigan sozlama; u dasturga "qo'shimcha ravishda shuni ham qil" deydi. --print-bytecode — bytecode'ni chiqar, --print-bytecode-filter=jami — faqat jami funksiyasinikini:
node --print-bytecode --print-bytecode-filter=jami jami.jsNode 24.21 dagi haqiqiy chiqish (xotira manzillari qisqartirildi — ular har ishga tushirishda boshqacha):
[generated bytecode for function: jami (… <SharedFunctionInfo jami>)]
Bytecode length: 6
Parameter count 3
Register count 0
Frame size 0
30 S> … @ 0 : 0b 04 Ldar a1
42 E> … @ 2 : 41 03 00 Mul a0, [0]
49 S> … @ 5 : b3 Return
Constant pool (size = 0)
Handler Table (size = 0)
70000Qo'rqmang — o'qish uchun faqat uchta qator muhim. Ignition'da bitta maxsus "qo'l" bor — akkumulyator: oxirgi natija shunda turadi. a0 — birinchi parametr (narx), a1 — ikkinchisi (soni):
| Buyruq | Ma'nosi |
|---|---|
Ldar a1 |
soni ni akkumulyatorga ol (Load accumulator) |
Mul a0, [0] |
akkumulyatorni narx ga ko'paytir |
Return |
akkumulyatordagi qiymatni qaytar |
return narx * soni — uchta buyruq bo'ldi. Parameter count 3 da uchinchisi — yashirin this. Chapdagi 30, 42, 49 — manba fayldagi belgi o'rni: xato chiqsa, dvigatel shu orqali qaysi qatorni ko'rsatishni biladi.
3.2 [0] — kuzatuv daftari
Mul a0, [0] dagi [0] ga e'tibor bering. Bu — fikr-mulohaza (feedback) uyasi. Ignition har ko'paytirishda shu uyaga yozib boradi: "bu yerga qanday turdagi qiymatlar keldi?" Hozircha — doim butun son.
Bu — kelajak uchun yozuvlar. Rustam aka ham kartochkaga qarab pishirar ekan, daftarga belgilab boradi: "osh — kuniga 200 ta, doim guruch bilan". Daftar to'lgach, u nimani yod olish kerakligini biladi.
Tekshirib ko'ring:
return narx + soniuchun bytecode'da qaysi buyruqMulo'rnida bo'ladi deb o'ylaysiz? Tekshirib ko'rish uchun qaysi buyruqni ishga tushirasiz?
Javob
Add a0, [0] — qo'shish. Tekshirish: jami.js da * ni + ga almashtirib, node --print-bytecode --print-bytecode-filter=jami jami.js ni qayta ishga tushirasiz. Taxminni doim o'lchov yoki haqiqiy chiqish bilan tasdiqlang — bu darsning asosiy odati.
4. JIT: issiq kodni tezlashtirish
4.1 To'rt bosqich
Interpretator ishonchli, lekin sekin: har buyruqni har safar "o'qib" bajaradi. Rustam aka ham kartochkaga qarab pishirsa, har qadamda to'xtab, keyingi qatorni o'qiydi.
Tezroq yo'l — kompilyator (compiler): kodni bir yo'la protsessorning o'z buyruqlariga — mashina kodiga — o'girib qo'yadigan dastur. Oshxonada bu — retseptni yod olish. Yod olishga vaqt ketadi, lekin keyin har osh tezroq pishadi.
V8 ikkalasini birga ishlatadi. Avval interpretator bilan boshlaydi, keyin ish davomida, dastur ishlab turganda, ko'p chaqiriladigan kodni kompilyatsiya qiladi. Bu JIT kompilyatsiya (Just-In-Time, "aynan kerak paytda") deyiladi. Issiq (hot) funksiya — ko'p chaqiriladigan yoki ichida uzoq sikl aylanadigan funksiya. Oshxonada — har kuni yuzlab buyurtma qilinadigan osh.
V8 da 2023 yildan to'rt bosqich bor:
| Bosqich | Nima qiladi | Oshxonada | Qachondan |
|---|---|---|---|
| Ignition | bytecode'ni interpretatsiya qiladi | kartochkaga qarab | 2016–2017 |
| Sparkplug | bytecode'ni tez, optimallashtirmasdan mashina kodiga o'giradi | kartochkani o'qimay, ketma-ket | 2021 |
| Maglev | tez optimallashtiruvchi kompilyator | asosiy qadamlarni yod olgan | Chrome 117, 2023 |
| TurboFan | eng kuchli, lekin sekin kompilyatsiya qiluvchi | to'liq yod, ortiqcha harakatsiz | 2017 |
Optimallashtirish — kodni tezroq ishlaydigan qilib qayta tuzish: keraksiz tekshiruv va qadamlarni olib tashlash. Sparkplug buni qilmaydi — faqat "o'qish" vaqtini tejaydi. Maglev va TurboFan qiladi.
Manbalar: Sparkplug, 2021-05-27, Maglev, 2023-12-05. Maglev haqidagi maqolada V8 jamoasi uni shunday ta'riflaydi: Sparkplug va TurboFan o'rtasida, "yetarlicha yaxshi kodni yetarlicha tez" beradigan kompilyator.
Nega bitta eng kuchli kompilyator emas? Chunki kompilyatsiyaning o'zi ham vaqt oladi. TurboFan yaxshi kod beradi, lekin uzoq o'ylaydi. Bir-ikki marta chaqiriladigan funksiya uchun bu zarar. Shuning uchun funksiya "qiziganicha" pog'onama-pog'ona ko'tariladi:
flowchart LR
K["Manba kod"] --> P["Parser"]
P --> A["AST"]
A --> I["Ignition: bytecode"]
I -- "iliq" --> S["Sparkplug"]
S -- "issiq" --> M["Maglev"]
M -- "juda issiq" --> T["TurboFan"]
M -. "deopt" .-> I
T -. "deopt" .-> IUzuq chiziqli o'qlar — orqaga qaytish (deoptimizatsiya). Unga «Deoptimizatsiya» bo'limida kelamiz.
4.2 Har bosqich qanchalik tez
Bitta funksiyani har bosqichni o'chirib-yoqib o'lchadik. Funksiya million narxni QQS bilan yig'adi:
// tier.mjs — o'lchov: vaqt har kompyuterda har xil
function chekJami(narxlar) {
let jami = 0;
for (let i = 0; i < narxlar.length; i++) {
jami += Math.round(narxlar[i] * 1.12);
}
return jami;
}
const narxlar = Array.from(
{ length: 1_000_000 },
(_, i) => 5000 + (i % 1000),
);
for (let k = 0; k < 5; k++) chekJami(narxlar); // isitish
const vaqtlar = [];
for (let k = 0; k < 9; k++) {
const boshi = performance.now();
chekJami(narxlar);
vaqtlar.push(performance.now() - boshi);
}
vaqtlar.sort((a, b) => a - b);
console.log(vaqtlar[4].toFixed(2), "ms"); // o'rtadagisiBosqichlarni Node bayroqlari bilan o'chirdik: --no-sparkplug, --no-maglev, --no-turbofan. Har variant 7 marta ishga tushirildi, grafikda — mediana (o'rtadagi qiymat; Performansni o'lchash darsida batafsil):
- faqat Ignition107 ms
- Ignition + Sparkplug80 ms
- + Maglev (TurboFan'siz)8,5 ms
- hammasi (standart)7,3 ms
Manba: O'lchov: Node 24.21 (V8 13.6), 12th Gen Intel Core i5-12500H, Windows 11, 2026-10-05; performance.now, har variant 7 marta, mediana
Nimaga qarang:
- Sparkplug interpretatordan taxminan chorak tez — u optimallashtirmaydi, faqat "o'qish" ishini olib tashlaydi.
- Maglev — keskin sakrash: taxminan 12 barobar. Optimallashtiruvchi kompilyator turlarni biladi va keraksiz tekshiruvlarni tashlaydi.
- TurboFan bu oddiy siklda Maglev'dan ozgina tezroq. Murakkab kodda farq kattaroq bo'ladi.
Sizning kompyuteringizda millisekundlar boshqacha chiqadi, lekin nisbat o'xshash bo'ladi. Muhim xulosa: oddiy kodingiz, hech narsa qilmasangiz ham, ish davomida o'nlab barobar tezlashadi.
Diqqat: Bu bayroqlar faqat o'rganish va o'lchash uchun. Real dasturda ularni ishlatmang — standart sozlama eng yaxshisi.
5. Optimallashtirishni ko'rish: --trace-opt
5.1 Funksiya qanday "ko'tariladi"
Endi jarayonni jonli kuzatamiz. jami ni 100 000 marta son bilan chaqiramiz, oxirida esa bir marta satr beramiz:
// jami.js — Node bayroqlari bilan ishga tushiriladi
function jami(narx, soni) {
return narx * soni;
}
for (let i = 0; i < 100_000; i++) {
jami(35000, i % 5);
}
console.log("--- endi satr beramiz ---");
console.log(jami("35000", 2)); // 70000Trace ("iz") — dvigatel o'z ishini qatorma-qator yozib boradigan kundalik. Uni uchta bayroq yoqadi: --trace-baseline (Sparkplug), --trace-opt (Maglev va TurboFan), --trace-deopt (deoptimizatsiya):
node --trace-baseline --trace-opt --trace-deopt jami.jsChiqish uzun — Node'ning ichki funksiyalari ham optimallashadi. Faqat jami ga tegishli qatorlarni oldik, manzillarni … bilan qisqartirdik (Node 24.21):
[Baseline batch compilation] Enqueued SFI jami with estimated size 42 (current budget: 4018/4096)
[marking … <JSFunction jami …> for optimization to MAGLEV, ConcurrencyMode::kConcurrent, reason: hot and stable]
[compiling method … <JSFunction jami …> (target MAGLEV), mode: ConcurrencyMode::kConcurrent]
[Concurrent Sparkplug Off Thread] Function … <SharedFunctionInfo jami> installed
[completed compiling … <JSFunction jami …> (target MAGLEV) - took 0.000, 0.164, 0.001 ms]
[marking … <JSFunction jami …> for optimization to TURBOFAN_JS, ConcurrencyMode::kConcurrent, reason: hot and stable]
[compiling method … <JSFunction jami …> (target TURBOFAN_JS), mode: ConcurrencyMode::kConcurrent]
[completed compiling … <JSFunction jami …> (target TURBOFAN_JS) - took 0.015, 1.669, 0.022 ms]
[completed optimizing … <JSFunction jami …> (target TURBOFAN_JS)]
[bailout (kind: deopt-eager, reason: not a Smi): begin. deoptimizing … <JSFunction jami …>, … <Code TURBOFAN_JS>, …]Qatorma-qator o'qiymiz:
Baseline batch compilation … Enqueued—jamiSparkplug navbatiga qo'yildi;installed— Sparkplug kodi tayyor.marking … for optimization to MAGLEV … reason: hot and stable— "issiq va barqaror": funksiya ko'p chaqirildi va unga keladigan turlar o'zgarmayapti. Maglev'ga belgilandi.ConcurrencyMode::kConcurrent— kompilyatsiya fonda, alohida thread'da bo'ladi (Sinxron va asinxron kod darsidagi thread — bajarilish ipi). Dastur to'xtamaydi, kod tayyor bo'lgach almashtiriladi. Rustam aka oshni kartochka bilan pishirishda davom etadi, shogirdi esa yonida retseptni yod oldirib turadi.took 0.000, 0.164, 0.001 ms— Maglev bir millisekundning beshdan biricha vaqt sarfladi. TurboFan esa (1.669) taxminan 10 barobar ko'p. «To'rt bosqich» bo'limidagi "TurboFan uzoq o'ylaydi" gapining isboti.bailout … reason: not a Smi—bailout("chiqib ketish") — tez koddan voz kechish. Satr keldi va tez kod tashlab yuborildi. Bu keyingi bo'limning mavzusi.
console.log qatorlari ham chiqadi, lekin chiqishni faylga yoki grep ga yo'naltirganda ular boshqa joyda turib qolishi mumkin: V8 o'z xabarlarini alohida buferdan yozadi.
5.2 Har safar bir xil emas
Bir qiziq narsa: biz shu faylni 10 marta ishga tushirdik. 6 marta jami TurboFan'gacha yetdi, 4 marta Maglev'da qolib, deoptimizatsiya Maglev kodidan bo'ldi. Sabab — kompilyatsiya fondagi thread'da: sikl tugashidan oldin TurboFan ulgurdimi-yo'qmi, kompyuterning shu paytdagi bandligiga bog'liq.
Bu muhim saboq: dvigatel xatti-harakati deterministik emas. Bir xil kod har ishga tushirishda biroz boshqacha tezlikda ishlashi mumkin. Shuning uchun o'lchovni bir marta emas, ko'p marta qilamiz.
Tekshirib ko'ring:
reason: hot and stabledagi "stable" (barqaror) so'zi nimani anglatadi va u qayerdan bilinadi?
Javob
Funksiyaga keladigan qiymatlar turi o'zgarmay qolgan. Buni V8 bytecode'dagi fikr-mulohaza uyalaridan ([0] kabi) biladi: Ignition har amalda qanday tur kelganini yozib borgan. Uyalar "doim butun son" desa — funksiya barqaror.
6. Deoptimizatsiya
6.1 Taxmin buzilganda
Optimallashtiruvchi kompilyator taxmin bilan ishlaydi. jami ga 100 000 marta butun son keldi. TurboFan o'yladi: "bu yerga doim butun son keladi". U narx * soni ni protsessorning eng tez butun son ko'paytirishiga aylantirdi. Faqat bitta qisqa tekshiruv qoldirdi: "kirgan qiymat haqiqatan butun sonmi?"
jami("35000", 2) da tekshiruv muvaffaqiyatsiz tugadi. Tez kod endi noto'g'ri — u satrni ko'paytira olmaydi. V8 shu zahoti tez kodni tashlab, funksiyani Ignition'ga qaytardi. Natija baribir to'g'ri chiqdi (70000): JavaScript qoidasi bo'yicha "35000" * 2 satrni songa aylantiradi. Bu deoptimizatsiya (deoptimization, "deopt") deyiladi. Rustam aka "grechka bilan osh" buyurtmasini olib, yod olgan usulni qo'yib, kartochkaga qaytdi.
not a Smi ning ma'nosi: Smi (small integer) — V8 ning ichki "kichik butun son" turi. U alohida obyekt yaratmasdan, to'g'ridan-to'g'ri saqlanadi va eng tez ishlaydi. Tekshiruv "Smi kutgan edim, boshqa narsa keldi" dedi.
Quyidagi qadamlarda butun hayot yo'lini kuzating. Kod — o'sha jami.js. Bosqichlar ketma-ketligi — yuqoridagi haqiqiy trace'dan:
6.2 Deopt qachon zarar
Bitta deoptimizatsiya — muammo emas. V8 funksiyani keyinroq yangi, kengroq ma'lumot bilan qayta optimallashtiradi. Muammo — funksiya optimallashib, deopt bo'lib, yana optimallashib, yana deopt bo'lib turganda. Har aylanishda kompilyatsiyaga vaqt ketadi, kod esa ko'pincha sekin bosqichda ishlaydi.
Deopt sabablari ko'pincha shulardan:
- funksiyaga goh son, goh satr kelishi (yuqoridagi misol);
- butun sonlar yig'indisi Smi chegarasidan oshib ketishi (trace'da
reason: overflow); - obyektlarning "shakli" har xil bo'lishi — keyingi darsning mavzusi;
- massivga kutilmagan turdagi element qo'shilishi.
Qoida oddiy: bitta funksiyaga bir xil turdagi ma'lumot bering. Bu tezlik uchun ham, o'qish uchun ham yaxshi. Narxni goh son, goh satr qilib uzatadigan kod baribir xatoga moyil. Bu odatni TypeScript (15-qism) majburiy qiladi.
6.3 Butun yo'l bitta jadvalda
Darsdagi har atamani oshxonadagi o'xshashi bilan yonma-yon qo'yamiz. Biror qator tushunarsiz bo'lsa — o'sha bo'limga qayting:
| V8 da | Oshxonada |
|---|---|
| manba kod | yangi retsept |
| tahlil (parsing), AST | retseptni o'qib, qadamlarga ajratish |
| bytecode | qisqa qadamlar kartochkasi |
| Ignition | kartochkaga qarab pishirish |
| fikr-mulohaza uyasi | "osh doim guruch bilan" daftari |
| JIT (Sparkplug, Maglev, TurboFan) | ko'p so'raladigan taomni yod olish |
| deoptimizatsiya | "grechka bilan" buyurtmasi — kartochkaga qaytish |
Tekshirib ko'ring: Quyidagi funksiya
trace-deoptda deopt ko'rsatadimi?function ikkiBarobar(x) { return x * 2; }— avval 100 000 marta1.5,2.5kabi kasr sonlar, keyin bir marta3bilan chaqirildi.
Javob
Yo'q. Biz buni 5 marta node --trace-opt --trace-deopt bilan tekshirdik — ikkiBarobar uchun birorta ham deopt chiqmadi. V8 uchun kasr son (double) — "kengroq" tur: butun son unga sig'adi. Tez kod double kutadi va 3 ni double sifatida qabul qila oladi. Aksincha — avval faqat butun son, keyin kasr — deopt beradi (2-mashqda ko'rasiz).
7. Ko'p uchraydigan xatolar
7.1 "Dvigatelni aldash" uchun kodni buzish
Internetda "for forEach dan tez", "x | 0 Math.floor dan tez" kabi maslahatlar ko'p. Ularning ko'pi eski V8 versiyalari uchun yozilgan va hozir noto'g'ri. V8 har yili o'zgaradi: 2021 da Sparkplug, 2023 da Maglev qo'shildi. Keyin, 2025 yilda, TurboFan ichki tuzilmasining katta qismi yangi Turboshaft bilan almashtirildi (V8 blogi, "Land ahoy: leaving the Sea of Nodes", 2025-03-25). Tuzatish: o'qiladigan kod yozing. Tezlik muammosi bo'lsa — o'lchang (Performansni o'lchash va benchmarking).
7.2 Bayroqlarni real dasturda ishlatish
--no-turbofan, --jitless yoki --allow-natives-syntax bilan server ishga tushirish. Birinchi ikkitasi dasturni o'nlab barobar sekinlashtiradi (grafikka qarang). Tuzatish: bu bayroqlar — o'rganish va tashxis uchun. --jitless ning yagona real sababi — xavfsizlik talabi qattiq muhitlar (V8 blogi, "JIT-less V8", 2019-03-13).
7.3 Trace chiqishini aniq raqam deb o'qish
took 0.164 ms yoki "6 marta TurboFan" — bitta kompyuter, bitta lahzaning natijasi. Tuzatish: trace'dan nima bo'lganini o'qing (qaysi bosqich, qanday sabab), aniq raqamni emas.
7.4 SyntaxError ni bajarilish xatosi deb o'ylash
"Xato 200-qatorda, lekin 1-qatordagi console.log ham chiqmayapti — Node buzilgan". Yo'q: sintaksis xatosi tahlil bosqichida chiqadi. Tuzatish: SyntaxError ko'rsangiz — ko'rsatilgan qatordagi grammatikani tekshiring, oldingi qatorlarning bajarilishini emas.
8. Mashqlar
1-mashq (oson): Bytecode'ni o'qing
chegirma.js faylini yarating va uning bytecode'ini chiqaring:
function chegirmaliNarx(narx) {
return narx - 5000;
}
console.log(chegirmaliNarx(35000)); // 30000Ishora: buyruq «Bytecode nima» bo'limida — --print-bytecode-filter ga funksiya nomini bering. Qaysi buyruq ayirishni bajaradi? 5000 qayerdan olinadi?
Yechim
node --print-bytecode \
--print-bytecode-filter=chegirmaliNarx chegirma.jsNode 24.21 dagi chiqishning asosiy qismi:
Parameter count 2
34 S> … @ 0 : 0b 03 Ldar a0
46 E> … @ 2 : 00 4c 88 13 00 00 SubSmi.Wide [5000], [0]
53 S> … @ 8 : b3 ReturnLdar a0 — narx ni akkumulyatorga oladi. SubSmi.Wide [5000], [0] — akkumulyatordan 5000 ni ayiradi. Bu yerda ayirish uchun alohida, maxsus buyruq bor: SubSmi — "kichik butun sonni ayir". Son bytecode'ning o'ziga yozilgan, .Wide esa "son katta, ko'proq bayt kerak" degani. [0] — yana fikr-mulohaza uyasi. Return — natijani qaytaradi. Konsolda oxirida 30000 ham chiqadi.
Taxmin qilgandingizmi, Sub chiqadi deb? Biz ham shunday o'ylagan edik — haqiqiy chiqish boshqacha bo'lib chiqdi. Shuning uchun dvigatel haqidagi har bir gapni ishga tushirib tekshiramiz.
2-mashq (o'rta): Deoptni o'zingiz chaqiring
jami.js dagi jami("35000", 2) qatorini olib tashlang. Uning o'rniga jami(35000.5, 2) yozing va --trace-opt --trace-deopt bilan ishga tushiring. jami uchun deopt bormi? reason nima?
Yechim
Bizning ishga tushirishimizda (Node 24.21) deopt bor edi va sababi yana not a Smi: kasr son ham Smi emas. Tez kod faqat butun sonlarga moslangan edi. Faqat jami qatorlarini ko'rish uchun Git Bash'da:
node --trace-opt --trace-deopt jami.js | grep jamiXulosa: "son" ham bitta tur emas. V8 ichida butun son va kasr son — ikki xil tur. Narxlarni butun so'mda saqlash (tiyinsiz) — tezlik uchun ham, yaxlitlash xatolari uchun ham foydali.
3-mashq (qiyin): Bosqichlarni o'lchang
«Har bosqich qanchalik tez» bo'limidagi tier.mjs ni ko'chiring. Uni to'rt xil ishga tushiring va har birini kamida 5 marta takrorlang: --no-sparkplug --no-maglev --no-turbofan, --no-maglev --no-turbofan, --no-turbofan, bayroqsiz. Har variant uchun o'rtadagi (medianani) yozing. Sizda ham Maglev qo'shilganda eng katta sakrash bo'ldimi?
Yechim
Git Bash'da bitta variantni besh marta ishga tushirish:
for i in 1 2 3 4 5; do node --no-turbofan tier.mjs; doneBesh natijani o'sish tartibida yozing — o'rtadagisi (3-si) mediana. Bizda: faqat Ignition ≈ 107 ms, + Sparkplug ≈ 80 ms, + Maglev ≈ 8,5 ms, hammasi ≈ 7,3 ms (i5-12500H, Windows 11). Sizda raqamlar boshqacha bo'ladi, lekin optimallashtiruvchi kompilyator (Maglev) qo'shilganda eng katta sakrash bo'lishi kerak. Bo'lmasa — ehtimol kompyuter shu paytda band bo'lgan; qayta o'lchang.
9. Real ishda
- Chrome DevTools "Performance" panelida funksiyalar yonida "Compile" va "Optimize" belgilari ko'rinadi — bu shu darsdagi bosqichlar. Panelni DevTools: Performance, Memory va Network darsida o'rganamiz.
- Node serverlari ishga tushgandan keyin birinchi so'rovlar sekinroq bo'ladi — kod hali "qizimagan". Katta kompaniyalar yangi serverni trafikka qo'shishdan oldin uni sun'iy so'rovlar bilan isitadi (warm-up).
- Kutubxona mualliflari (React, Lodash) issiq funksiyalarni bir xil turdagi argument bilan ishlashga moslab yozadi.
- Intervyu: "Interpretator va kompilyator farqi?", "JIT nima?", "Deoptimizatsiya nima va qachon bo'ladi?" — middle frontend/Node intervyularida uchraydi.
Xulosa
- Dvigatel kodni avval tahlil qiladi (tokenlar → AST); sintaksis xatosi bo'lsa — birorta qator ham bajarilmaydi.
- AST'dan bytecode yasaladi; uni Ignition interpretatori bajaradi va fikr-mulohaza uyalariga turlarni yozib boradi.
- Issiq kod pog'onama-pog'ona JIT bilan mashina kodiga o'giriladi: Sparkplug → Maglev → TurboFan; bizning o'lchovda Ignition'dan hammasi yoqilgancha — taxminan 15 barobar tezlik.
- Optimallashtirish taxminga tayanadi; taxmin buzilsa — deoptimizatsiya: tez kod tashlanadi, Ignition davom ettiradi.
- Qoida: funksiyaga bir xil turdagi ma'lumot bering va tezlikni taxmin qilmay, o'lchang.
Keyingi dars: Hidden classes va inline caching — V8 obyekt "shakli"ni qanday eslab qolishini va nega bir xil tuzilishdagi obyektlar tezroq ekanini ko'ramiz.
Manbalar
- V8 blogi: "Launching Ignition and TurboFan" (2017-05-15), "Sparkplug — a non-optimizing JavaScript compiler" (2021-05-27) — v8.dev/blog
- V8 blogi: "Maglev - V8's Fastest Optimizing JIT" (2023-12-05), "Land ahoy: leaving the Sea of Nodes" (2025-03-25)
- V8 blogi: "Blazingly fast parsing, part 1: optimizing the scanner" (2019-03-25), "part 2: lazy parsing" (2019-04-15), "JIT-less V8" (2019-03-13)
- Node.js hujjatlari:
node --v8-options— barcha V8 bayroqlari ro'yxati - AST Explorer — astexplorer.net
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!