Hidden Classes (V8)
V8'ning hidden class mexanizmi: dinamik JS obyektlarni tezkor xotira murojaati uchun qanday 'shape'larga aylantirishi, bir xil tartibda property qo'shilgan obyektlar bir xil hidden class'ni ulashishi, va tartibni buzish yoki delete ishlatish nega deoptimizatsiyaga olib kelishi.
64-dars
Hidden Classes (V8)
Hidden class — V8'ning ichki, dasturchiga ko'rinmaydigan mexanizmi bo'lib, u JavaScript'dagi dinamik obyektlarni, xuddi statik tildagi (masalan, C++) classlardek qattiq, tezkor tuzilmaga aylantirib, xotiraga murojaatni tezlashtiradi.
Buni tushunish uchun avval muammoni ko'raylik. JavaScript — dinamik til: obyektga istalgan vaqt yangi property qo'shish, o'chirish mumkin:
const obj = {};
obj.x = 1;
obj.y = 2;
Aslida bu C++ yoki Java'dagi kabi statik strukturaga ega emas — property'lar runtime'da paydo bo'ladi. Muammo: agar V8 har safar obj.xga murojaat qilganda, "x qayerda saqlangan?" deb butun obyektni (hash-jadval kabi) qidirishi kerak bo'lsa — bu juda sekin bo'lardi. Hidden class'lar aynan shu muammoni hal qilish uchun yaratilgan.
1. Nega bu kerak bo'lgan?
V8 — JavaScript'ni kompilyatsiya qiladigan dvigatel (JIT — keyingi mavzuda chuqur ko'ramiz). Kompilyatsiya qilingan tezkor kod yozish uchun V8'ga obyektning qat'iy tuzilmasi (qaysi property qayerda joylashgan) kerak. Lekin JS obyektlari dinamik bo'lgani uchun, bunday qat'iylik yo'q — ko'rinishda.
V8'ning yechimi: har safar obyekt yaratilganda yoki unga yangi property qo'shilganda, V8 fonda, dasturchiga ko'rinmas holda, o'sha obyekt uchun hidden class (ichki hujjatlarda ba'zan "Map" ham deyiladi — Map obyektidan farqli, chalkashtirmang) yaratadi. Bu hidden class — "bu tur obyektlarda qaysi property qaysi xotira joyida (offset) joylashgan" degan jadval.
function Nuqta(x, y) {
this.x = x;
this.y = y;
}
const p1 = new Nuqta(1, 2);
V8 buni bosqichma-bosqich shunday kuzatadi:
1-qadam: new Nuqta() chaqirilganda, V8 bo'sh hidden class yaratadi — chaqiraylik C0 (hech qanday property yo'q).
2-qadam: this.x = x bajarilganda, V8 yangi hidden class yaratadi — C1, unda: "x property offset 0'da joylashgan". V8 C0'dan C1'ga o'tish (transition) yozib qo'yadi.
3-qadam: this.y = y bajarilganda, yana yangi hidden class — C2: "x — offset 0, y — offset 1". C1'dan C2'ga yana transition.
C0 (bo'sh)
│ +x
▼
C1 (x: offset 0)
│ +y
▼
C2 (x: offset 0, y: offset 1)
p1 obyekti endi C2 hidden class'iga ishora qiladi. Bu — muhim g'oya: obyektning o'zi property qiymatlarini (1, 2) saqlaydi, lekin qaysi property qayerda ekanligi haqidagi ma'lumot — hidden class'da saqlanadi, alohida.
2. Nega bu tezlik beradi
Endi ikkinchi obyekt yarataylik:
const p2 = new Nuqta(3, 4);
p2 yaratilganda, V8 xuddi shunday x keyin y qo'shiladi — va V8 yangi hidden class yaratmaydi, balki p1 yasashda hosil bo'lgan xuddi shu C0 → C1 → C2 zanjiridan qayta foydalanadi. Demak p1 va p2 — ikkalasi ham **bir xil hidden class (C2)**ga ega bo'ladi.
Bu nima beradi: agar dasturda minglab Nuqta obyekti bo'lsa va hammasi bir xil hidden class'ga ega bo'lsa, V8 (JIT kompilyatori orqali) bitta optimallashtirilgan mashina kodi yozadi: "x — har doim offset 0'da, y — har doim offset 1'da" — va bu kodni barcha Nuqta obyektlariga qo'llaydi, hech qanday qidiruv-tekshiruvsiz. Bu — Inline Caching (IC) deb ataladigan mexanizmning asosi (buni JIT mavzusida chuqurroq ko'ramiz).
Agar hidden class bo'lmaganida, V8 har safar p1.x yoki p2.xga murojaat qilganda, "x" nomli propertyni obyekt ichida (hash-jadval kabi) qidirishi kerak bo'lardi — bu sezilarli darajada sekinroq.
3. Muammo: Hidden class'ni "buzish" (deoptimizatsiyaga olib boruvchi holat)
Endi eng muhim amaliy xulosa. Agar siz obyektlarni turlicha tartibda yoki turlicha to'plamda property bilan yaratsangiz — ular turli hidden classlarga ega bo'lib qoladi, va V8 optimallashtira olmaydi:
function Nuqta1() {
this.x = 1;
this.y = 2;
}
function Nuqta2() {
this.y = 2; // tartib teskari!
this.x = 1;
}
const a = new Nuqta1(); // hidden class: x(0), y(1)
const b = new Nuqta2(); // hidden class: y(0), x(1) — BOSHQA hidden class!
Garchi a va b tashqi ko'rinishda bir xil ({x: 1, y: 2}) bo'lsa ham, V8 uchun ular butunlay boshqa turdagi obyektlar — chunki property'lar qo'shilish tartibi boshqacha. Agar shu ikki obyekt bitta funksiyaga (masalan, masofa(a, b)) uzatilsa, V8 bitta optimallashtirilgan yo'l tuza olmaydi — ikkita alohida "shape"ni boshqarishga majbur bo'ladi, bu esa sekinlashtiradi ("polymorphic", keyin "megamorphic" holat — JIT mavzusida ko'ramiz).
Boshqa keng tarqalgan xato — dinamik ravishda property qo'shish yoki o'chirish:
const obj = { x: 1, y: 2 };
obj.z = 3; // yangi hidden class yaratiladi (transition)
delete obj.x; // BU ESA — juda yomon holat
delete operatori hidden class zanjirini buzadi — V8 obyektni "dictionary mode" (hash-jadval rejimi)ga o'tkazishga majbur bo'ladi, bu esa hidden class orqali beriladigan barcha optimallashtirishni yo'qqa chiqaradi. Shuning uchun performance-kritik kodda delete obj.prop o'rniga obj.prop = undefined ishlatish tavsiya etiladi (agar mumkin bo'lsa).
// YOMON — turli tartibda property qo'shish
function User1(name) { this.name = name; }
function User2(name) { this.age = 0; this.name = name; }
// YAXSHI — constructor'da BARCHA property'larni bir xil tartibda, boshidanoq belgilash
function User(name, age) {
this.name = name;
this.age = age;
}
Qoida: obyektlarni bir xil "shape" (bir xil property'lar, bir xil tartibda) bilan yarating — ayniqsa katta miqdorda bir xil turdagi obyekt yaratiladigan joylarda (masalan, massiv ichida minglab element). Bu V8'ga bitta hidden class'dan qayta-qayta foydalanish, va shu orqali kodni monomorphic (bitta shape bilan ishlaydigan) qilib optimallashtirish imkonini beradi.