Ders 21 / 25
Sanal Ağaç Tabanlı Çerçeveler
Durumdan yeni bir ağaç üretip öncekiyle karşılaştıran güncelleme modeli; bir hücrelik değişikliğin ürettiği iş miktarının sayılması, anahtarın uzlaşmadaki rolü ve bellemenin ödünleşimi.
İçindekiler
Önceki konu boyunca bileşenler kâğıt üzerinde birleştirildi: bir işlev bir ağaç üretti, ağaçlar iç içe geçti, özellikler birleşti. Üretilen ağacın belgeye nasıl yansıdığına hiç bakılmadı.
Bu soruya tek bir yanıt yoktur. Çerçeveler, durum değiştiğinde belgenin hangi bölümünün güncelleneceğini bulmak için birbirinden ayrı yollar izler ve bu yol, çerçevenin öğrenme eğrisinden başarım profiline kadar her şeyini belirler. Bu konu dört yolu özellikleriyle ele alır. İlki, güncellemeyi bir karşılaştırma problemi olarak gören ailedir.
Yeniden Çizim Birimi Bileşendir
Modelin adımları şöyledir. Bir bileşenin durumu değişir. Çerçeve o bileşenin işlevini yeniden çalıştırır; işlev, güncel durumdan yeni bir ağaç üretir. Bu ağaç belge değildir — belgeyi tarif eden bellek içi bir yapıdır ve sanal ağaç adıyla anılır. Çerçeve yeni ağacı bir önceki ağaçla karşılaştırır, farkları bulur ve yalnızca farkları belgeye yazar.
Modelin kazancı, bileşen yazan kişinin belgeye hiç dokunmamasıdır. Bildirimsel Oluşturma dersindeki tek yönlü akış burada tam anlamıyla geçerlidir: durumdan görünüme bir işlev vardır, tersi yoktur.
Bedeli ise şu cümlede saklıdır: yeniden çizim birimi bileşendir. Bir bileşenin durumundaki değişiklik, o bileşenin ürettiği ağacın tamamının yeniden üretilmesine ve karşılaştırılmasına yol açar; değişen bölüm ne kadar küçük olursa olsun.
Bir Hücrelik Değişikliğin Maliyeti
İstasyon sayfasındaki ölçüm tablosunda iki yüz satır var. Tek bir sensörün değeri güncelleniyor. Bunun ürettiği iş sayılabilir.
// sanal-agac.mjs — yeniden cizim, fark alma ve bir hucrelik degisikligin maliyeti let sayac; const sifirla = () => (sayac = { bilesen: 0, dugum: 0, oznitelik: 0, yama: 0, atlanan: 0 }); const h = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat().filter((c) => c != null) }); const yazi = (d) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: String(d) }); // --- Bilesenler: her cizimde yeniden calisir ve yeni bir agac uretir --- const Hucre = ({ deger }) => { sayac.bilesen++; return h("td", { class: "deger" }, yazi(`${deger.toFixed(1)} °C`)); }; const Satir = ({ ad, deger }) => { sayac.bilesen++; return h("tr", { anahtar: ad }, h("th", { scope: "row" }, yazi(ad)), Hucre({ deger })); }; const Tablo = ({ satirlar, ciz }) => { sayac.bilesen++; return h("table", { class: "olcum" }, h("caption", {}, yazi("Kuzey Yamaç")), h("tbody", {}, ...satirlar.map(ciz))); }; // --- Belleme: ozellikleri degismeyen bilesen onceki agacini aynen dondurur --- function belle(bilesen) { const onbellek = new Map(); return (o) => { const anahtar = o.ad; const kayit = onbellek.get(anahtar); if (kayit && Object.keys(o).every((a) => kayit.ozellikler[a] === o[a])) { sayac.atlanan++; return kayit.agac; } const agac = bilesen(o); onbellek.set(anahtar, { ozellikler: { ...o }, agac }); return agac; }; } // --- Fark alma --- function fark(eski, yeni) { if (eski === yeni) { sayac.dugum++; return; } // ayni basvuru: alt agaca hic inilmez sayac.dugum++; if (eski.ad !== yeni.ad) { sayac.yama++; return; } if (eski.metin !== undefined || yeni.metin !== undefined) { if (eski.metin !== yeni.metin) sayac.yama++; return; } for (const a of new Set([...Object.keys(eski.ozn), ...Object.keys(yeni.ozn)])) { sayac.oznitelik++; if (eski.ozn[a] !== yeni.ozn[a]) sayac.yama++; } const anahtarli = yeni.cocuklar.length > 0 && yeni.cocuklar.every((c) => c.ozn?.anahtar !== undefined); if (!anahtarli) { const n = Math.max(eski.cocuklar.length, yeni.cocuklar.length); for (let i = 0; i < n; i++) { if (!eski.cocuklar[i] || !yeni.cocuklar[i]) { sayac.yama++; continue; } fark(eski.cocuklar[i], yeni.cocuklar[i]); } return; } const eskiHarita = new Map(eski.cocuklar.map((c, i) => [c.ozn.anahtar, { dugum: c, sira: i }])); const eskiSira = yeni.cocuklar.map((c) => eskiHarita.get(c.ozn.anahtar)?.sira ?? -1); const yerindeKalan = enUzunArtanAltDizi(eskiSira); // tasinmasi gerekmeyen dugumler yeni.cocuklar.forEach((c, i) => { const bulunan = eskiHarita.get(c.ozn.anahtar); if (!bulunan) { sayac.yama++; return; } // yeni dugum: eklenir if (!yerindeKalan.has(i)) sayac.yama++; // en az sayida tasima fark(bulunan.dugum, c); eskiHarita.delete(c.ozn.anahtar); }); sayac.yama += eskiHarita.size; // kalanlar silinir } // En uzun artan alt dizi: tasinmasi gerekmeyen dugumlerin dizinlerini verir. function enUzunArtanAltDizi(dizi) { const oncul = new Array(dizi.length).fill(-1); const kuyruk = []; for (let i = 0; i < dizi.length; i++) { if (dizi[i] < 0) continue; let alt = 0, ust = kuyruk.length; while (alt < ust) { const orta = (alt + ust) >> 1; if (dizi[kuyruk[orta]] < dizi[i]) alt = orta + 1; else ust = orta; } if (alt > 0) oncul[i] = kuyruk[alt - 1]; kuyruk[alt] = i; } const sonuc = new Set(); for (let k = kuyruk[kuyruk.length - 1]; k !== undefined && k !== -1; k = oncul[k]) sonuc.add(k); return sonuc; } const raporla = (baslik) => console.log(`${baslik.padEnd(36)} ${String(sayac.bilesen).padStart(9)} ${String(sayac.atlanan).padStart(8)} ` + `${String(sayac.dugum).padStart(9)} ${String(sayac.oznitelik).padStart(11)} ${String(sayac.yama).padStart(5)}`); const SATIR_SAYISI = 200; const veri = (kaydirma = 0) => Array.from({ length: SATIR_SAYISI }, (_, i) => ({ ad: `sensör-${String(i).padStart(3, "0")}`, deger: -10 + i * 0.1 + kaydirma })); console.log("senaryo bileşen atlanan düğüm öznitelik yama"); // 1. Belleme yok: tek hucre degisiyor. sifirla(); const v1 = veri(); const eski1 = Tablo({ satirlar: v1, ciz: Satir }); const v2 = v1.map((s, i) => (i === 120 ? { ...s, deger: 42.5 } : s)); const yeni1 = Tablo({ satirlar: v2, ciz: Satir }); fark(eski1, yeni1); raporla("bellemesiz, tek hücre değişti"); // 2. Belleme var: ozellikleri degismeyen satirlar atlanir. sifirla(); const bellenmisSatir = belle(Satir); const eski2 = Tablo({ satirlar: v1, ciz: bellenmisSatir }); const yeni2 = Tablo({ satirlar: v2, ciz: bellenmisSatir }); fark(eski2, yeni2); raporla("bellemeli, tek hücre değişti"); // 3. Hicbir sey degismedi (ust bilesen baska bir nedenle cizildi). sifirla(); const eski3 = Tablo({ satirlar: v1, ciz: Satir }); const yeni3 = Tablo({ satirlar: v1, ciz: Satir }); fark(eski3, yeni3); raporla("bellemesiz, hiçbir şey değişmedi"); // 4. Basa bir satir eklendi — anahtarli uzlasma. sifirla(); const yeniSatir = { ad: "sensör-yeni", deger: 3.3 }; const eski4 = Tablo({ satirlar: v1, ciz: Satir }); const yeni4 = Tablo({ satirlar: [yeniSatir, ...v1], ciz: Satir }); fark(eski4, yeni4); raporla("başa satır eklendi (anahtarlı)"); // 5. Ayni ekleme, anahtar yokken: her satir kaydigi icin karsilastirma eslesmiyor. sifirla(); const SatirAnahtarsiz = ({ ad, deger }) => { sayac.bilesen++; return h("tr", {}, h("th", { scope: "row" }, yazi(ad)), Hucre({ deger })); }; const eski5 = Tablo({ satirlar: v1, ciz: SatirAnahtarsiz }); const yeni5 = Tablo({ satirlar: [yeniSatir, ...v1], ciz: SatirAnahtarsiz }); fark(eski5, yeni5); raporla("başa satır eklendi (anahtarsız)");
senaryo bileşen atlanan düğüm öznitelik yama bellemesiz, tek hücre değişti 802 0 1004 601 1 bellemeli, tek hücre değişti 404 199 208 4 1 bellemesiz, hiçbir şey değişmedi 802 0 1004 601 0 başa satır eklendi (anahtarlı) 804 0 1004 601 1 başa satır eklendi (anahtarsız) 804 0 1004 401 401
Sayıların Okunması
İlk satır modelin özetidir. Belgeye yazılan tek bir yama var; ona ulaşmak için 802 bileşen çağrısı, 1004 düğüm karşılaştırması ve 601 öznitelik karşılaştırması yapıldı. Oran yaklaşık bin altı yüze bir.
Bu bir kusur değil, modelin tanımıdır. Çerçeve neyin değiştiğini bilmez; öğrenmek için karşılaştırır. Karşılaştırmanın maliyeti değişikliğin büyüklüğüyle değil, üretilen ağacın büyüklüğüyle orantılıdır.
Üçüncü satır bunu en açık biçimde gösteriyor: hiçbir şey değişmediğinde de aynı 802 çağrı ve 1004 karşılaştırma yapılıyor, yalnızca sonuçta sıfır yama çıkıyor. Değişiklik olmaması işi azaltmıyor.
Sayılar iki bileşen daha içeriyor. Bileşen çağrısı sayısı, üretilen ağacın yeniden kurulmasıdır; her çağrı yeni nesneler ayırır ve öbekte iş üretir. Düğüm karşılaştırması ise ağacı gezme maliyetidir. İkisi ayrı ayrı ölçülür, çünkü ayrı ayrı azaltılırlar.
Anahtar Uzlaşmanın Girdisidir
Dördüncü ve beşinci satırlar arasındaki fark, bu ailedeki en somut karar noktasını gösteriyor.
Başa bir satır eklendiğinde, anahtar varsa uzlaşma her satırı kimliğiyle eşleştirir. Eski sıradaki 200 satır yeni sırada da aynı göreli düzendedir; taşınması gereken düğüm yoktur ve tek yama yeni satırın eklenmesidir. (Örnekteki taşıma sayımı en uzun artan alt dizi hesabına dayanır: yerinde kalabilecek en büyük düğüm kümesi bulunur, yalnızca dışarıda kalanlar taşınır.)
Anahtar yoksa karşılaştırma konuma göre yapılır. Yeni listenin birinci öğesi eski listenin birinci öğesiyle, ikincisi ikincisiyle eşleştirilir. Başa ekleme her satırı bir konum kaydırdığı için hiçbir eşleşme tutmaz: 200 satırın adı ve değeri farklı çıkar ve 401 yama üretilir. Belgede aslında tek bir satır eklenmiş olsa bile, 401 yazma işlemi yapılır.
Sonucun ötesinde bir yan etkisi daha vardır. Konuma göre eşleşen düğümler yeniden kullanıldığı için, o düğümlere bağlı belge durumu — odak, kaydırma konumu, girdi alanının yazılmış içeriği — yanlış satırda kalır. Bu, listelerde anahtar kullanmanın bir başarım tercihi değil, doğruluk gereği olmasının nedenidir.
Bellemenin Ödünleşimi
İkinci satır, işin nasıl azaltıldığını gösteriyor. Satır bileşeni bellendiğinde, özellikleri değişmeyen 199 satır yeniden çalışmıyor ve önceki ağacını aynen döndürüyor. Fark alma bu düğümlere ulaştığında başvuruların aynı olduğunu görüp alt ağaca hiç inmiyor: düğüm karşılaştırması 1004’ten 208’e, öznitelik karşılaştırması 601’den 4’e düşüyor.
Kazanç büyük, ama koşullu. Belleme yalnızca özelliklerin kimliği korunduğunda çalışır. Üst bileşen her çizimde yeni bir nesne ya da yeni bir işlev üretip özellik olarak veriyorsa, sığ karşılaştırma her seferinde başarısız olur ve belleme hiçbir şey kazandırmaz — üstelik karşılaştırmanın kendi maliyetini ekler. Türetilmiş Değerler dersindeki kimlik kararlılığı sorunu, bu ailede bir başarım aracına dönüşür.
İkinci bedel görünürlüktür. Belleme elle yapılan bir işlemdir: hangi bileşenin belleneceğine ve hangi değerlerin kimliğinin korunacağına insan karar verir. Karar yanlış verildiğinde sonuç bir hata değil, sessiz bir yavaşlamadır.
Ailenin Profili
Bu ailenin özelliklerini birkaç maddede toplamak, sonraki derslerdeki karşılaştırmanın temelini verir.
Güncelleme birimi bileşendir; ince tanelilik bileşen sınırına kadar iner, daha aşağı inmez. Çalışma zamanı, fark alma algoritmasını ve ağaç temsilini taşımak zorundadır; bu, indirilen kodun sabit bir bölümüdür. Bileşen işlevi saf bir dönüşüm olarak yazıldığı için üretim işi bölünebilir: çerçeve karşılaştırmayı parçalara ayırıp aralara başka iş sokabilir, çünkü sanal ağaç belgeye yazılmadan önce hiçbir görünür etki yaratmaz. Buna karşılık aynı saflık, değişikliğin nereden geldiğini kaydeden bir defter tutulmadığı anlamına gelir; bilgi her güncellemede karşılaştırmayla yeniden üretilir.
Özet
- Bu ailede güncelleme, durumdan yeni bir ağaç üretip önceki ağaçla karşılaştırmaktır; yeniden çizim birimi bileşendir.
- Karşılaştırmanın maliyeti değişikliğin büyüklüğüyle değil üretilen ağacın büyüklüğüyle orantılıdır: iki yüz satırlık tabloda tek hücrelik değişiklik 802 bileşen çağrısı ve 1004 düğüm karşılaştırması üretti, belgeye tek yama yazıldı.
- Hiçbir şey değişmediğinde de aynı iş yapılır; yalnızca yama sayısı sıfır çıkar.
- Anahtar, uzlaşmanın kimlik girdisidir: anahtarlı listede başa ekleme tek yama, anahtarsız listede 401 yama üretti ve konuma göre eşleşen düğümlerde belge durumu yanlış satırda kalır.
- Belleme, özellikleri değişmeyen alt ağaçlara hiç inilmemesini sağlar; örnekte düğüm karşılaştırması 1004’ten 208’e indi.
- Belleme özelliklerin kimlik kararlılığına bağlıdır ve elle yapılır; yanlış uygulandığında sessizce etkisiz kalır.
Sonraki Adım
Bu ailedeki iş miktarının kaynağı tek bir eksikti: çerçeve neyin değiştiğini bilmiyordu, bulmak için karşılaştırıyordu. Bilseydi karşılaştırmaya hiç gerek kalmazdı. Bilmenin yolu, bir değerin nerede okunduğunu okuma anında kaydetmektir — o zaman değer yazıldığında, güncellenmesi gereken yerlerin listesi zaten elde olur. Sonraki ders, bu defteri çalışma zamanında tutan aileyi ele alır ve aynı iki yüz satırlık tabloda aynı hücre değiştiğinde yapılan iş miktarını bu dersteki sayılarla karşılaştırır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.