IlmHamroh
JavaScript Full-stack/12-qism. JavaScript: ilg'or mavzular va kod sifati29/44-dars22 daqiqa
Mundarija (28)

Hidden classes va inline caching: V8 obyekt shaklini qanday eslab qoladi

Qisqacha: V8 har obyekt uchun yashirin "shakl" (hidden class) yaratadi: qaysi xususiyat, qaysi tartibda va xotiraning qayerida turadi. Bir xil shakldagi obyektlar bitta shaklni bo'lishadi, va xususiyatni o'qiydigan kod shaklni eslab qoladi (inline cache) — keyingi safar qidirmasdan, to'g'ridan-to'g'ri oladi. Kodga 1–4 xil shakl kelsa — tez, 5 va undan ko'p bo'lsa — bizning o'lchovda taxminan 3 barobar sekin. Qoida: obyektlarni bir xil tartibda, bir joyda yarating, delete ishlatmang.

Bu darsda

  • Hidden class (shakl) nima ekanini va u qachon o'zgarishini tushuntira olasiz.
  • node --allow-natives-syntax va %HaveSameMap bilan ikki obyekt bir xil shakldami — o'zingiz tekshira olasiz.
  • Inline cache holatlarini (monomorfik, polimorfik, megamorfik) farqlay olasiz va ularning tezlik farqini o'lchangan raqamlar bilan aytib bera olasiz.
  • Massivning elements kind'ini (SMI, DOUBLE, HOLEY) bilasiz va nega massivni "teshiksiz" yaratish kerakligini tushuntira olasiz.

Oldin bilishingiz kerak: JS dvigateli ichida, Massivga o'xshash obyektlar va teshikli massivlar, Class maydonlari.

1. Nega bu kerak?

Oldingi darslarda uchta va'da qolgan edi:

Bugun bu gaplar nega rost ekanini ko'ramiz — va o'lchaymiz.

«Bahor»dan o'xshatish. Ofitsiant Otabek oshxonaga buyurtma varaqasini beradi. Oshxonada bosma varaqa bor: yuqorida stol raqami, o'rtada taom nomi, pastda soni. Oshpaz Rustam aka varaqaga qaramasdan biladi: "soni — har doim pastki o'ng burchakda". U ko'zini to'g'ri o'sha joyga tashlaydi.

Endi tasavvur qiling: har ofitsiant buyurtmani o'zicha, istalgan qog'ozga, istalgan tartibda yozadi. Rustam aka har varaqni boshidan o'qib, "soni" qayerdaligini qidirishi kerak. Oshxona sekinlashadi. Hidden class — o'sha bosma varaqa shabloni. Inline cache — Rustam akaning "soni pastki o'ngda" degan odati.

2. Hidden class: obyektning shakli

2.1 Nomlar alohida, qiymatlar alohida

Oddiy obyektni olaylik: { nom: "Osh", narx: 35000 }. Agar dvigatel har obyektda "nom" va "narx" so'zlarini qayta-qayta saqlasa — million obyektda million marta bir xil so'z yotgan bo'lardi. Va har taom.narx da "narx qayerda?" deb qidirish kerak bo'lardi.

V8 boshqacha qiladi. U nomlar ro'yxatini alohida tuzilmada saqlaydi: "1-o'rin — nom, 2-o'rin — narx". Bu tuzilma hidden class (yashirin klass) yoki shakl (shape) deyiladi; V8 ichida u "Map" deb ataladi (bizning Map kolleksiyasi bilan aloqasi yo'q). Obyektning o'zida esa faqat qiymatlar va shaklga havola turadi:

flowchart LR
  O["osh: shakl → S, «Osh», 35000"] --> S["Shakl S: nom → 0-o'rin, narx → 1-o'rin"]
  M["manti: shakl → S, «Manti», 30000"] --> S

Ikki obyekt — bitta shakl. Shakl bir marta saqlanadi va minglab obyekt uni bo'lishadi. Manba: V8 blogi, "Fast properties in V8", 2017-08-30 va Mathias Bynens, Benedikt Meurer — "JavaScript engine fundamentals: Shapes and Inline Caches" (2018-06-14). "Yashirin" deyilishiga sabab — uni JavaScript'dan ko'rib bo'lmaydi; bu faqat dvigatelning ichki ishi.

2.2 Shaklni o'z ko'zingiz bilan tekshirish

V8 da maxsus bayroq bor: --allow-natives-syntax. U % bilan boshlanadigan ichki funksiyalarni ochadi. Biz bittasini ishlatamiz: %HaveSameMap(a, b) — "ikki obyektning shakli bir xilmi?".

Diqqat: % funksiyalari JavaScript emas — faqat V8 ga xos va faqat shu bayroq bilan ishlaydi. Bayroqsiz fayl SyntaxError beradi. Ularni faqat o'rganish uchun ishlatamiz, real kodda — hech qachon.

shakl.js faylini yarating va uni bayroq bilan ishga tushiring (buyruq faylning birinchi qatorida ham yozilgan):

bash
node --allow-natives-syntax shakl.js
js
// node --allow-natives-syntax shakl.js
const osh = { nom: "Osh", narx: 35000 };
const manti = { nom: "Manti", narx: 30000 };
const choy = { narx: 5000, nom: "Ko'k choy" };

console.log("osh va manti:", %HaveSameMap(osh, manti));
console.log("osh va choy:", %HaveSameMap(osh, choy));

const lagmon = { nom: "Lag'mon", narx: 28000 };
lagmon.achchiq = true;
console.log("osh va lagmon:", %HaveSameMap(osh, lagmon));

Node 24.21 dagi haqiqiy chiqish:

text
osh va manti: true
osh va choy: false
osh va lagmon: false

Uch xulosa:

  • osh va manti — qiymatlari har xil, lekin shakli bir xil. Shakl qiymatga emas, nomlar va ularning tartibiga qaraydi.
  • choy da xuddi o'sha ikki xususiyat bor, lekin tartibi boshqa — shakl ham boshqa.
  • lagmon ga keyin achchiq qo'shildi — u yangi shaklga o'tdi.

Tekshirib ko'ring: const a = { narx: 1, nom: "x" }; va const b = { narx: 2, nom: "y" }; uchun %HaveSameMap(a, b) nima beradi? a va choy uchun-chi?

Javob

Ikkalasi ham true. a, b va choy — uchalasida avval narx, keyin nom. Tartib bir xil — shakl bir xil. Qiymatlar (1, 2, 5000) shaklga ta'sir qilmaydi.

3. Shakl qanday o'zgaradi

3.1 O'tishlar zanjiri

Obyektga xususiyat qo'shilganda V8 yangi shakl yaratadi va eskisidan unga o'tish (transition) yozib qo'yadi. Keyingi obyekt xuddi shu yo'ldan yursa — yangi shakl yaratilmaydi, tayyor o'tish ishlatiladi. Qadamlarda osh va manti qaysi shaklda ekanini kuzating:

Diagrammada butun daraxt — bitta ildizdan ikki shoxga bo'lingan:

flowchart TD
  S0["S0: bo'sh"] -- "+ nom" --> S1["S1: nom"]
  S1 -- "+ narx" --> S2["S2: nom, narx"]
  S0 -- "+ narx" --> S3["S3: narx"]
  S3 -- "+ nom" --> S4["S4: narx, nom"]

osh S2 da, manti S4 da. Agar ikkalasi bir yo'ldan yurganida — bitta shaklda bo'lardi. %HaveSameMap bilan tekshirsangiz: bir xil tartibda bosqichma-bosqich qurilgan ikki obyekt — true, osh va manti esa — false.

3.2 Klass bu muammoni o'zi hal qiladi

class konstruktori har nusxada maydonlarni bir xil tartibda yozadi. Demak, hamma nusxa bitta yo'ldan yuradi:

js
// node --allow-natives-syntax shakl2.js
class Taom {
  constructor(nom, narx) {
    this.nom = nom;
    this.narx = narx;
  }
}
const osh = new Taom("Osh", 35000);
const manti = new Taom("Manti", 30000);
const literal = { nom: "Osh", narx: 35000 };
console.log("osh va manti:", %HaveSameMap(osh, manti));
console.log("osh va literal:", %HaveSameMap(osh, literal));

Konsolda (Node 24.21):

text
osh va manti: true
osh va literal: false

osh va manti — bitta shakl. Literal esa nomlari va tartibi bir xil bo'lsa ham boshqa shaklda. Sabab — shaklga prototip ham kiradi: literal ning prototipi Object.prototype, osh niki esa Taom.prototype (Prototip zanjiri). Bu muammo emas: bitta funksiyaga odatda bir turdagi obyektlar keladi — yo hammasi Taom, yo hammasi literal.

3.3 delete — shaklni buzadigan amal

Xususiyatni olib tashlash kerak bo'ldi. Ikki yo'l bor: delete taom.narx yoki taom.narx = undefined. Natija o'xshash ko'rinadi, lekin dvigatel uchun keskin farq bor. Yana bir % funksiya: %HasFastProperties(obj) — "obyekt tez rejimdami?".

shakl3.js — Taom klassi o'sha, ostida yangi tajriba:

js
// node --allow-natives-syntax shakl3.js
class Taom {
  constructor(nom, narx) {
    this.nom = nom;
    this.narx = narx;
  }
}
const osh = new Taom("Osh", 35000);

const somsa = new Taom("Somsa", 8000);
delete somsa.narx;
console.log("somsa tez rejimdami:", %HasFastProperties(somsa));

const non = new Taom("Non", 4000);
non.narx = undefined;
console.log("non tez rejimdami:", %HasFastProperties(non));
console.log("osh va non:", %HaveSameMap(osh, non));

Konsolda:

text
somsa tez rejimdami: false
non tez rejimdami: true
osh va non: true

delete dan keyin somsa lug'at rejimiga (dictionary mode) o'tdi. Endi uning shakli umumiy emas. Xususiyatlar obyektning ichidagi alohida "nom → qiymat" jadvalida saqlanadi (xuddi Map kabi) va har o'qishda nom bo'yicha qidiriladi. non esa shaklini saqladi — osh bilan bir xil.

Oshxonadagi o'xshashi: Otabek bosma varaqadagi "soni" katagini qaychi bilan qirqib tashladi. Endi bu varaqa shablonga mos emas. Rustam aka uni boshidan oxirigacha o'qib chiqishga majbur. undefined esa katakni bo'sh qoldirishga o'xshaydi: varaqa shabloni buzilmaydi.

Qanchalik farq qiladi? 100 000 taomning narx ini yig'ib o'lchadik (har variant 5 marta ishga tushirildi, mediana):

100 000 obyektdan narx o'qish: delete va undefined, ms
  • oddiy obyektlar0,19 ms
  • chegirma = undefined0,18 ms
  • delete chegirmalug'at rejimi1,64 ms

Manba: O'lchov: Node 24.21 (V8 13.6), 12th Gen Intel Core i5-12500H, Windows 11, 2026-10-05; isitishdan keyin 21 o'lchov medianasi, 5 marta ishga tushirildi

Taxminan 9 barobar sekin. E'tibor bering: o'chirilgan xususiyat chegirma edi, biz esa narx ni o'qidik. delete faqat bitta xususiyatni emas — butun obyektni sekinlashtiradi.

Maslahat: Xususiyat "yo'q" ekanini bildirish kerak bo'lsa — null yoki undefined bering. Obyektdan haqiqatan nusxa kerak bo'lsa — rest bilan yangi obyekt yasang: const { narx, ...qolgani } = taom; (Obyekt destructuring). Kalitlar tez-tez qo'shilib-o'chiriladigan to'plam kerak bo'lsa — obyekt emas, Map ishlating.

Tekshirib ko'ring: Nega taom.narx = undefined dan keyin ham "narx" in taom true qaytaradi, delete dan keyin esa false?

Javob

undefined berish — xususiyatni o'chirmaydi, uning qiymatini almashtiradi. Xususiyat shaklda qoladi, in uni topadi. delete esa xususiyatni butunlay olib tashlaydi. Shuning uchun Object.keys va JSON.stringify natijasi ham farq qiladi: JSON.stringify undefined qiymatli kalitni yozmaydi, lekin u obyektda bor. Kod mantiqi uchun qaysi biri kerakligini tanlang — tezlik ikkinchi o'rinda.

4. Inline cache: shaklni eslab qolish

4.1 Kuzatuv uyasi nimani eslaydi

Oldingi darsda bytecode'da [0] uyasini ko'rgan edik — fikr-mulohaza uyasi. Xususiyat o'qishda (taom.narx) bu uya yana bir narsani eslaydi: qaysi shakl keldi va narx shu shaklning qaysi o'rnida. Keyingi safar xuddi shu shakl kelsa — qidiruv yo'q: "shakl S2 mi? Ha → 1-o'rindan ol". Bu inline cache (IC, "joyidagi kesh") deyiladi. "Inline" — chunki kesh kodning aynan shu joyiga (shu taom.narx ifodasiga) bog'langan.

Kodning bitta joyi qancha xil shaklni ko'rganiga qarab, IC to'rt holatda bo'ladi:

Holat Necha shakl ko'rgan Tezlik
uninitialized (boshlang'ich) hali hech biri —
monomorfik 1 eng tez
polimorfik 2–4 biroz sekinroq
megamorfik 5 va undan ko'p sezilarli sekin

"Mono" — bitta, "poli" — bir nechta, "mega" — juda ko'p. Polimorfik holatda IC bir nechta shaklni ro'yxatda saqlab, har kelganini ro'yxat bo'ylab solishtiradi. Megamorfikda esa ro'yxatdan voz kechadi va umumiy, sekinroq qidiruvga o'tadi.

Rustam akaning oshxonasida bu uch holat shunday ko'rinadi:

  • Monomorfik: oshxonada bitta bosma varaqa bor. Ko'z o'zi pastki o'ng burchakka boradi.
  • Polimorfik: varaqaning 2–4 xil nusxasi bor (eski, yangi, banket uchun). Rustam aka bir qarashda "bu qaysi nusxa?" deb taniydi va kerakli burchakka qaraydi.
  • Megamorfik: har ofitsiant o'z qog'ozida yozadi. Shablonlarni eslab qolish befoyda — har varaqni o'qib, "soni"ni qidiradi.

Quyidagi qadamlarda bitta taom.narx joyidagi IC qanday o'zgarishini kuzating. Har yangi tartib yoki yangi xususiyat — yangi shakl:

Bu qadamlar o'ylab topilmagan: keyingi bo'limda xuddi shu ketma-ketlikni V8 ning o'z logida ko'ramiz.

Nega aynan 4? Bu V8 sozlamasi. Node 24 dagi bayroqlar ro'yxatida uni ko'rish mumkin:

bash
node --v8-options | grep -A1 polymorphic-map-count
text
  --max-valid-polymorphic-map-count (maximum number of valid maps to track in POLYMORPHIC state)
        type: int  default: --max-valid-polymorphic-map-count=4

"POLYMORPHIC holatda kuzatiladigan shakllarning eng ko'p soni — 4".

4.2 IC holatlarini jonli ko'rish

V8 IC o'zgarishlarini logga yozishi mumkin. ic.js faylida bitta narxniOl funksiyasiga yetti xil obyekt beramiz. Birinchi ikkitasi bir xil shaklda, qolganlari — har biri yangi shaklda:

js
// ic.js
function narxniOl(taom) {
  return taom.narx;
}
narxniOl({ nom: "Osh", narx: 35000 });
narxniOl({ nom: "Manti", narx: 30000 });
narxniOl({ narx: 5000, nom: "Choy" });
narxniOl({ narx: 28000 });
narxniOl({ narx: 8000, achchiq: true });
narxniOl({ id: 1, narx: 9000 });
narxniOl({ id: 2, nom: "Somsa", narx: 8000 });
bash
node --log-ic --no-lazy-feedback-allocation \
  --logfile=ic.log --no-logfile-per-isolate ic.js
grep ",narx," ic.log

--no-lazy-feedback-allocation kerak, chunki V8 tejash uchun kuzatuv uyalarini funksiya bir necha marta chaqirilgandan keyingina yaratadi. Bayroq ularni birinchi chaqiruvdan yaratadi. Haqiqiy chiqish (Node 24.21, manzillar … bilan qisqartirildi):

text
LoadIC,…,2,15,0,1,…,narx,,
LoadIC,…,2,15,1,P,…,narx,,
LoadIC,…,2,15,P,P,…,narx,,
LoadIC,…,2,15,P,P,…,narx,,
LoadIC,…,2,15,P,N,…,narx,,
LoadIC,…,2,15,N,N,…,narx,,

Qanday o'qiladi: LoadIC — xususiyat o'qish keshi. 2,15 — fayldagi joy: 2-qator, 15-ustun, ya'ni taom.narx. Keyingi ikki belgi — eski holat → yangi holat: 0 — boshlang'ich, 1 — monomorfik, P — polimorfik, N — megamorfik.

Yetti chaqiruv, lekin oltita qator. Nega? Ikkinchi chaqiruv (manti) birinchisi bilan bir shaklda — IC uni darhol tanidi, hech narsa o'zgarmadi, logga yozilmadi. Mana butun yo'l:

stateDiagram-v2
  direction LR
  [*] --> Mono: osh (1-shakl)
  Mono --> Mono: manti (o'sha shakl)
  Mono --> Poli: choy (2-shakl)
  Poli --> Poli: 3- va 4-shakl
  Poli --> Mega: 5-shakl
  Mega --> Mega: 6-shakl

4.3 Qancha sekinlashadi

Endi o'lchaymiz. Million elementli massiv, har elementdan narx o'qiladi. Massivda 1, 2, 3 … 32 xil shakldagi obyektlar aralash keladi:

js
// ic-olchov.mjs — node ic-olchov.mjs 4  (4 — shakllar soni)
function shakllarniYasa(soni) {
  const natija = [];
  for (let s = 0; s < soni; s++) {
    const taom = {};
    taom["q" + s] = s; // har shaklda boshqa birinchi maydon
    taom.narx = 1000 + s;
    natija.push(taom);
  }
  return natija;
}
function jamiNarx(taomlar) {
  let jami = 0;
  for (let i = 0; i < taomlar.length; i++) {
    jami += taomlar[i].narx; // shu joydagi IC o'lchanadi
  }
  return jami;
}
const soni = Number(process.argv[2]);
const namunalar = shakllarniYasa(soni);
const taomlar = Array.from(
  { length: 1_000_000 },
  (_, i) => namunalar[i % soni],
);
for (let k = 0; k < 20; k++) jamiNarx(taomlar); // isitish
const vaqtlar = [];
for (let k = 0; k < 15; k++) {
  const boshi = performance.now();
  jamiNarx(taomlar);
  vaqtlar.push(performance.now() - boshi);
}
vaqtlar.sort((a, b) => a - b);
console.log(soni, vaqtlar[7].toFixed(2), "ms");

Har shakl soni uchun 7 marta ishga tushirdik, grafikda — mediana:

1 mln marta taom.narx o'qish: shakllar soniga qarab, ms
  • 1 shakl (monomorfik)1,7 ms
  • 2 shakl2,12 ms
  • 3 shakl2,31 ms
  • 4 shakl (polimorfik chegarasi)2,61 ms
  • 5 shakl (megamorfik)5,43 ms
  • 8 shakl5,39 ms
  • 32 shakl5,5 ms

Manba: O'lchov: Node 24.21 (V8 13.6), 12th Gen Intel Core i5-12500H, Windows 11, 2026-10-05; isitishdan keyin 15 o'lchov medianasi, har variant 7 marta ishga tushirildi

Grafik --max-valid-polymorphic-map-count=4 ni aniq tasdiqlaydi:

  • 1 dan 4 gacha — sekin-asta o'sish: har yangi shakl ro'yxatga bitta solishtirish qo'shadi.
  • 4 dan 5 ga — keskin sakrash, taxminan ikki barobar. Shu yerda IC megamorfikka o'tdi.
  • 5 dan keyin — tekis: megamorfik qidiruv uchun 5 ham, 32 ham bir xil.

Monomorfik va megamorfik orasida taxminan 3 barobar farq. Lekin raqamlarga diqqat qiling: bu million o'qish uchun 4 millisekund. Bitta tugma bosilganda bir necha yuz obyektni o'qiysiz — farq sezilmaydi. Shakl muhim bo'ladigan joy — yuz minglab elementli issiq sikllar.

Tekshirib ko'ring: function chiqar(x) { return x.nom; } ga faqat Taom klassi nusxalari keladi. Bu joydagi IC qaysi holatda bo'ladi? Agar ba'zan { nom: "Choy" } literal ham kelsa-chi?

Javob

Faqat Taom nusxalari — monomorfik: hammasi bitta shaklda (%HaveSameMap bilan ko'rdik). Literal ham kelsa — 2 shakl, polimorfik. Literal va klass nusxasi bir xil maydonli bo'lsa ham shakllari har xil, chunki prototiplari boshqa.

5. Massivlar: elements kinds

5.1 Massiv ham "shakl"ga ega

Massiv elementlari uchun V8 alohida belgi saqlaydi — elements kind (elementlar turi): massivda qanday qiymatlar borligi va teshik bormi. %DebugPrint(m) obyekt haqida ko'p ichki ma'lumot chiqaradi; biz undan faqat elements kind qatorini oldik:

js
// node --allow-natives-syntax turlar.js
const narxlar = [35000, 28000, 30000];
%DebugPrint(narxlar); // PACKED_SMI_ELEMENTS
narxlar.push(5000.5);
%DebugPrint(narxlar); // PACKED_DOUBLE_ELEMENTS
narxlar.push("bepul");
%DebugPrint(narxlar); // PACKED_ELEMENTS
narxlar.pop();
narxlar.pop();
%DebugPrint(narxlar); // PACKED_ELEMENTS — qaytmadi!

const teshikli = [1, 2, 3];
teshikli[10] = 4;
%DebugPrint(teshikli); // HOLEY_SMI_ELEMENTS

Izohlarda — Node 24.21 dagi haqiqiy elements kind qatorlari. Nomlarni o'qiymiz:

  • SMI — faqat kichik butun sonlar (oldingi darsdagi Smi). Eng tez.
  • DOUBLE — kasr sonlar ham bor.
  • ELEMENTS (prefikssiz) — har qanday qiymat: satr, obyekt. Eng umumiy, eng sekin.
  • PACKED — teshiksiz; HOLEY — teshikli (teshikli massivlar).

5.2 Faqat bir tomonga

Eng muhim qator — to'rtinchi %DebugPrint. "bepul" va 5000.5 ni pop bilan olib tashladik. Massivda yana faqat butun sonlar qoldi. Lekin tur PACKED_ELEMENTS bo'lib qoldi. Elements kind faqat umumiyroq tomonga o'zgaradi, hech qachon orqaga qaytmaydi.

Oshxonadagi javonni tasavvur qiling. Yorlig'ida "faqat piyolalar" deb yozilgan — Rustam aka qaramasdan qo'l uzatadi. Bir kuni javonga kosa ham qo'yildi — yorliq "piyola va kosalar" bo'ldi. Keyin choynak ham — "har xil idish". Choynak olib ketilgach ham hech kim yorliqni qayta yozmaydi: "yana choynak qo'yilishi mumkin". V8 ham shunday ehtiyot bo'ladi:

flowchart LR
  A["PACKED_SMI"] --> B["PACKED_DOUBLE"]
  B --> C["PACKED_ELEMENTS"]
  A --> D["HOLEY_SMI"]
  B --> E["HOLEY_DOUBLE"]
  C --> F["HOLEY_ELEMENTS"]
  D --> E
  E --> F

Manba: V8 blogi, "Elements kinds in V8", 2017-09-12. Teshik ayniqsa qimmat: teshikli massivda element o'qilganda dvigatel "bu yer teshikmi? Unda prototipdan qidirish kerak" deb qo'shimcha tekshiradi.

Amaliy qoidalar:

  • Massivni new Array(1000) bilan bo'sh yaratib, keyin to'ldirmang — u boshidan HOLEY. Array.from({ length: 1000 }, …) yoki push ishlating.
  • Massivga turli turdagi qiymatlarni aralashtirmang: narxlar massivida faqat son, "bepul" emas — 0 yoki alohida maydon.
  • m[m.length + 5] = x kabi "uzoqqa" yozmang — teshik paydo bo'ladi.

Tekshirib ko'ring: const m = [1, 2, 3]; m.push(-0); — massiv qaysi turga o'tadi deb o'ylaysiz? Qanday tekshirasiz?

Javob

-0 — matematik jihatdan nol, lekin Smi -0 ni ifodalay olmaydi: u faqat kasr son (double) sifatida saqlanadi. Shuning uchun PACKED_DOUBLE_ELEMENTS kutiladi. Tekshirish: %DebugPrint(m) va node --allow-natives-syntax. Taxmin — faqat taxmin; haqiqatni chiqish aytadi.

6. Ko'p uchraydigan xatolar

6.1 Obyektni har joyda har xil tartibda yaratish

Bir joyda { nom, narx }, boshqa joyda { narx, nom }, uchinchisida { nom } va keyin .narx = …. Xato chiqmaydi — kod ishlaydi, faqat bitta funksiyaga bir necha shakl boradi. Tuzatish: bir turdagi obyektni bitta joyda yarating — klass konstruktori yoki "yasovchi" funksiya (taomYasa(nom, narx)) bilan. Hamma maydonni boshidan bering, keyinchalik bo'ladiganlarini null bilan.

6.2 delete bilan tozalash

delete taom.vaqtinchalik — obyektni lug'at rejimiga o'tkazadi, o'qish taxminan 9 barobar sekinlashadi. Tuzatish: null/undefined bering yoki rest bilan yangi obyekt yasang. Kalitlar dinamik bo'lsa — Map.

6.3 Mikro-optimallashtirishga berilib ketish

Shu darsni o'qib, butun loyihani "shakl uchun" qayta yozish. Grafikka qayting: farq — million o'qishda millisekundlar. Tuzatish: avval kodni o'qiladigan qiling. Faqat o'lchov "shu issiq sikl sekin" desa — shaklga qarang.

6.4 % funksiyalarni kodda qoldirish

%HaveSameMap qolgan fayl bayroqsiz ishga tushirilsa — SyntaxError: Unexpected token '%'; brauzerda ham xuddi shunday. Tuzatish: tajriba fayllarini alohida saqlang, ular hech qachon loyihaga tushmasin.

7. Mashqlar

1-mashq (oson): Bir xil shaklmi?

shakl.js ni ko'chiring va oxiriga uchta obyekt qo'shing: const a = { nom: "Osh", narx: 35000, soni: 2 };, const b = { nom: "Manti", narx: 30000 }; va b.soni = 1;. Ikki savol: %HaveSameMap(a, b) nima beradi? b ni a bilan bir shaklga keltirish uchun nimani o'zgartirish kerak?

Yechim
js
// node --allow-natives-syntax shakl.js
const a = { nom: "Osh", narx: 35000, soni: 2 };
const b = { nom: "Manti", narx: 30000 };
b.soni = 1;
console.log(%HaveSameMap(a, b)); // false

const c = { nom: "Manti", narx: 30000, soni: 1 };
console.log(%HaveSameMap(a, c)); // true

Node 24.21 da birinchisi false, ikkinchisi true. Tartib bir xil bo'lsa ham, literalda yaratilgan va keyin qo'shilgan xususiyat — boshqa yo'l: literal hamma maydonni birdaniga, obyektning ichida joylaydi. Tuzatish — soni ni ham literalning o'zida berish.

2-mashq (o'rta): delete ni almashtiring

Quyidagi funksiya buyurtmadan vaqtinchalik maydonni olib tashlaydi. Uni delete siz qayta yozing: asl obyekt o'zgarmasin, natijada tmp bo'lmasin.

js
function tozala(buyurtma) {
  delete buyurtma.tmp;
  return buyurtma;
}
console.log(tozala({ id: 7, taom: "Osh", tmp: "x" }));
// { id: 7, taom: 'Osh' }

Ishora: rest sintaksisi — const { tmp, ...qolgani } = obyekt; (Obyekt destructuring).

Yechim
js
function tozala(buyurtma) {
  const { tmp, ...qolgani } = buyurtma;
  return qolgani;
}

const asl = { id: 7, taom: "Osh", tmp: "x" };
const toza = tozala(asl);
console.log(toza);
console.log("tmp" in asl, "tmp" in toza);

Konsolda:

text
{ id: 7, taom: 'Osh' }
true false

Asl obyekt o'zgarmadi (immutable uslub — Immutability chuqur), natija esa yangi obyekt: u lug'at rejimida emas, oddiy tez shaklda.

3-mashq (qiyin): Megamorfik chegarani o'zingiz toping

«Qancha sekinlashadi» bo'limidagi ic-olchov.mjs ni ko'chiring. Uni 3, 4, 5, 6 shakl bilan ishga tushiring, har birini 5 marta. Har biri uchun medianani yozing. Sakrash qaysi ikki son orasida bo'ldi? Keyin uni --max-valid-polymorphic-map-count=8 bayrog'i bilan qayta o'lchang — sakrash siljidimi?

Yechim

Git Bash'da:

bash
for n in 3 4 5 6; do
  for i in 1 2 3 4 5; do node ic-olchov.mjs $n; done
done

Bizda (Node 24.21, i5-12500H): 3 shakl ≈ 2,3 ms, 4 ≈ 2,6 ms, 5 ≈ 5,4 ms, 6 ≈ 5,4 ms — sakrash 4 va 5 orasida. Bayroq bilan:

bash
node --max-valid-polymorphic-map-count=8 ic-olchov.mjs 5

Bizda bayroq bilan: 5 shakl ≈ 3,6 ms, 8 ≈ 4,5 ms, 9 ≈ 7,7 ms (5 martadan mediana) — sakrash 8 va 9 orasiga siljidi. Bu mashqning maqsadi — V8 sozlamasini o'lchov bilan "ko'rish". Real loyihada bu bayroqni o'zgartirmaysiz.

8. Real ishda

  • Freymvorklar ichida. React (17-qismdan o'rganiladigan freymvork) o'z ichki obyektlarini (komponent "tolasi") har doim bir xil maydonlar bilan, konstruktorda yaratadi — monomorfik bo'lsin deb. Kutubxona kodini o'qiganda ko'p null bilan boshlangan maydonlarni ko'rasiz — sababi shu.
  • Ma'lumot bilan ishlash. Serverdan kelgan million qatorli JSON'ni qayta ishlaganda hamma obyektlarni bitta shaklga keltirish (bir xil kalitlar, bir xil tartib) issiq sikllarni sezilarli tezlashtiradi.
  • TypeScript (15-qism) obyekt turini e'lon qildiradi — bu tabiiy ravishda bir xil shakllarga olib keladi.
  • Intervyu: "Hidden class nima?", "Nega delete sekin?", "Monomorfik kod nima?" — Node va performansga oid intervyularda so'raladi.

Xulosa

  • Hidden class (shakl) — xususiyat nomlari va ularning o'rni; bir xil nomlar, bir xil tartib va bir xil prototip — bitta shakl.
  • Xususiyat qo'shish shaklni o'zgartiradi (o'tish); tartib ham shaklning bir qismi; klass konstruktori hamma nusxaga bitta shakl beradi.
  • delete obyektni lug'at rejimiga o'tkazadi — o'qish bizning o'lchovda taxminan 9 barobar sekinlashdi; o'rniga null/undefined yoki rest.
  • Inline cache kodning har joyida ko'rilgan shakllarni eslaydi: 1 — monomorfik, 2–4 — polimorfik, 5+ — megamorfik (taxminan 3 barobar sekin).
  • Massivning elements kind'i (SMI → DOUBLE → ELEMENTS, PACKED → HOLEY) faqat umumiyroq tomonga o'zgaradi.

Keyingi dars: Xotira boshqaruvi va garbage collection — obyektlar xotirada qayerda yashashi va dvigatel keraksizlarini qanday topib o'chirishini ko'ramiz.

Manbalar

  • V8 blogi: "Fast properties in V8" (2017-08-30), "Elements kinds in V8" (2017-09-12) — v8.dev/blog
  • Mathias Bynens, Benedikt Meurer: "JavaScript engine fundamentals: Shapes and Inline Caches" (2018-06-14) — mathiasbynens.be/notes/shapes-ics
  • Node.js: node --v8-options — --max-valid-polymorphic-map-count, --log-ic
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Hidden classes va inline caching: V8 obyekt shaklini qanday eslab qoladi — IlmHamroh