İçeriğe geç
academia.sh

Ders 22 / 25

Tepkisel Bağımlılık Takibi

Okuma anında kaydedilen bağımlılıkla kurulan ince taneli güncelleme; aynı tablo değişikliğinin bu ailede ürettiği iş miktarı, kayıt defterinin bedeli, elmas bağımlılıkta tutarlılık ve toplu yazma.

İçindekiler

Önceki dersteki iş miktarının kaynağı tek bir eksikti: çerçeve neyin değiştiğini bilmiyor, bulmak için karşılaştırıyordu. Bilseydi karşılaştırmaya gerek kalmazdı.

Bilmenin yolu, bir değerin nerede okunduğunu okuma anında kaydetmektir. Bir hesap çalışırken hangi değerlere baktığı izlenir ve bu değerlerin her birine “beni okuyan şuydu” kaydı düşülür. Değer yazıldığında, güncellenmesi gereken hesapların listesi zaten elde olur. Bu aile, güncellemeyi bir arama problemi olmaktan çıkarıp bir bildirim problemine çevirir.

Bağlantı, Bileşen Değil

Bu ailede güncelleme birimi bileşen değildir. Bileşen işlevi bir kez çalışır, tepkisel değerleri ve görünümün bu değerlere bağlandığı noktaları kurar, sonra bir daha çalışmaz. Güncellenen şey, tek bir bağlantıdır: belgedeki bir metin düğümü, bir öznitelik ya da bir sınıf adı.

Üç ilkel yeter. Sinyal (signal), okunabilen ve yazılabilen bir değerdir. Türetilmiş değer, başka değerlerden hesaplanan ve sonucu bellenen bir değerdir. Etki, kaynakları değiştiğinde yeniden çalışan bir işlevdir; görünüme yazan kod buradadır. Bileşen Yaşam Döngüsü ve Durum konusundaki üç kavramın aynısıdır; ayrım, burada bunların bileşene değil doğrudan birbirine bağlanmasıdır.

Aynı Değişiklik, Bu Kez Sayılmış

Önceki dersteki tabloyu aynı büyüklükte kuralım: iki yüz satır, tek bir hücrenin değeri değişiyor.

// sinyal.mjs — okuma anında kaydedilen bagimlilik ve ince taneli guncelleme
let sayac = { abonelik: 0, hesap: 0, etki: 0, yama: 0 };
const sifirla = () => (sayac = { abonelik: 0, hesap: 0, etki: 0, yama: 0 });

let izleyici = null;
const kuyruk = new Set();
const KIP = { sirali: true };   // false: degisiklik anlik ve derinlemesine yayilir
let topluDerinlik = 0;
const toplu = (fn) => { topluDerinlik++; try { fn(); } finally { if (--topluDerinlik === 0) bosalt(); } };

const kaydet = (kaynak) => {
  if (!izleyici) return;
  kaynak.aboneler.add(izleyici);
  izleyici.seviye = Math.max(izleyici.seviye, kaynak.seviye + 1);
  sayac.abonelik++;
};
const isaretle = (hesap) => {
  if (KIP.sirali) { if (!hesap.kirli) { hesap.kirli = true; kuyruk.add(hesap); } }
  else { hesap.kirli = true; hesap.calistir(); }
};
function bosalt() {
  while (kuyruk.size) {
    let en = null;
    for (const h of kuyruk) if (!en || h.seviye < en.seviye) en = h;   // seviye sirasi
    kuyruk.delete(en);
    if (en.kirli) en.calistir();
  }
}

function sinyal(ilk) {
  const d = { deger: ilk, aboneler: new Set(), seviye: 0 };
  return {
    oku() { kaydet(d); return d.deger; },
    yaz(v) {
      if (Object.is(v, d.deger)) return;
      d.deger = v;
      for (const a of [...d.aboneler]) isaretle(a);
      if (topluDerinlik === 0) bosalt();
    },
  };
}

function turetilmis(fn) {
  const d = { deger: undefined, aboneler: new Set(), seviye: 0, kirli: true };
  d.calistir = () => {
    const onceki = izleyici; izleyici = d;
    sayac.hesap++;
    const yeni = fn();
    izleyici = onceki;
    const degisti = !Object.is(yeni, d.deger);
    d.deger = yeni; d.kirli = false;
    if (degisti) for (const a of [...d.aboneler]) isaretle(a);
  };
  return { oku() { if (d.kirli) d.calistir(); kaydet(d); return d.deger; } };
}

function etki(fn) {
  const d = { aboneler: new Set(), seviye: 0, kirli: false };
  d.calistir = () => { const onceki = izleyici; izleyici = d; sayac.etki++; d.kirli = false; fn(); izleyici = onceki; };
  d.calistir();
  return d;
}

// --- 1. Iki yuz satirlik olcum tablosu: her hucre kendi baglantisini kurar ---
const SATIR_SAYISI = 200;
sifirla();
const belge = new Array(SATIR_SAYISI).fill("");
const sinyaller = Array.from({ length: SATIR_SAYISI }, (_, i) => sinyal(-10 + i * 0.1));
for (let i = 0; i < SATIR_SAYISI; i++) {
  etki(() => { belge[i] = `${sinyaller[i].oku().toFixed(1)} °C`; sayac.yama++; });
}
const esikAsan = turetilmis(() => sinyaller.filter((s) => s.oku() > 5).length);
let ozetMetni = "";
etki(() => { ozetMetni = `eşiği aşan: ${esikAsan.oku()}`; sayac.yama++; });
const kurulum = { ...sayac };

sifirla();
sinyaller[120].yaz(42.5);
const guncelleme = { ...sayac };

console.log("aşama       abonelik  türetilmiş hesap  etki çalışması  belge yazması");
const yaz = (ad, s) => console.log(`${ad.padEnd(11)} ${String(s.abonelik).padStart(8)} ${String(s.hesap).padStart(17)} ${String(s.etki).padStart(15)} ${String(s.yama).padStart(14)}`);
yaz("kurulum", kurulum);
yaz("güncelleme", guncelleme);
console.log("değişen hücrenin metni:", belge[120], "| özet:", ozetMetni);

// --- 2. Toplu yazma: iki yazma tek guncelleme ---
sifirla();
sinyaller[10].yaz(60);
sinyaller[11].yaz(70);
console.log("\niki ayrı yazma → türetilmiş hesap:", sayac.hesap, "| etki çalışması:", sayac.etki, "| özet:", ozetMetni);
sifirla();
toplu(() => { sinyaller[12].yaz(80); sinyaller[13].yaz(90); });
console.log("tek toplu yazma → türetilmiş hesap:", sayac.hesap, "| etki çalışması:", sayac.etki, "| özet:", ozetMetni);

// --- 3. Elmas bagimlilik: ayni kaynaktan turetilen iki deger ve onlari birlestiren ucuncu ---
const kutuk = [];
const kur = () => {
  const ham = sinyal(-4.2);
  const santigrat = turetilmis(() => `${ham.oku().toFixed(1)} °C`);
  const fahrenhayt = turetilmis(() => `${(ham.oku() * 1.8 + 32).toFixed(1)} °F`);
  const ozet = turetilmis(() => `${santigrat.oku()} / ${fahrenhayt.oku()}`);
  etki(() => kutuk.push(ozet.oku()));
  return ham;
};

console.log("\n--- seviye sirali yayilim ---");
KIP.sirali = true; kutuk.length = 0; sifirla();
kur().yaz(10);
console.log("etki çalışması:", sayac.etki, "| günlük:", JSON.stringify(kutuk));

console.log("\n--- anlik derinlemesine yayilim ---");
KIP.sirali = false; kutuk.length = 0; sifirla();
kur().yaz(10);
console.log("etki çalışması:", sayac.etki, "| günlük:", JSON.stringify(kutuk));
KIP.sirali = true;
aşama       abonelik  türetilmiş hesap  etki çalışması  belge yazması
kurulum          401                 1             201            201
güncelleme       202                 1               2              2
değişen hücrenin metni: 42.5 °C | özet: eşiği aşan: 50

iki ayrı yazma → türetilmiş hesap: 2 | etki çalışması: 4 | özet: eşiği aşan: 52
tek toplu yazma → türetilmiş hesap: 1 | etki çalışması: 3 | özet: eşiği aşan: 54

--- seviye sirali yayilim ---
etki çalışması: 2 | günlük: ["-4.2 °C / 24.4 °F","10.0 °C / 50.0 °F"]

--- anlik derinlemesine yayilim ---
etki çalışması: 3 | günlük: ["-4.2 °C / 24.4 °F","10.0 °C / 24.4 °F","10.0 °C / 50.0 °F"]

İki Ailenin Sayıları

Güncelleme satırı önceki dersle yan yana konduğunda ayrım keskindir. Sanal ağaç ailesinde tek hücrelik değişiklik 802 bileşen çağrısı, 1004 düğüm karşılaştırması ve 601 öznitelik karşılaştırması üretmişti. Burada bir türetilmiş hesap, iki etki çalışması ve iki belge yazması var. Ağaç hiç gezilmiyor, hiçbir karşılaştırma yapılmıyor.

İki belge yazmasının biri değişen hücre, diğeri eşik özeti. Özet, iki yüz sinyalin tamamına bağlı bir türetilmiş değerdir; bu yüzden hangi hücre değişirse değişsin yeniden hesaplanır. Toplayan hesaplar ince taneliliğin dışında kalır: bir değer her şeye bağlıysa, her değişiklikte çalışır. Ailenin kazancı bağlantıların yerelliğinden gelir, veri akışının yapısını değiştirmez.

Sayının küçüklüğü ölçek büyüdükçe daha da belirginleşir. Sanal ağaç ailesinde iş miktarı satır sayısıyla doğru orantılıdır; burada değişen bağlantı sayısıyla orantılıdır. İki bin satırlık bir tabloda ilk sayı on katına çıkar, ikincisi değişmez.

Defterin Bedeli

Kurulum satırı bedeli gösteriyor: 401 abonelik, 201 etki çalışması. Her hücre için bir etki, her etki için bir abonelik; artı özet hesabının iki yüz sinyale birden abone olması.

Bu, bellekte tutulan bir çizgedir. Her sinyal abone kümesi taşır, her hesap kaynak listesi taşır. Sanal ağaç ailesinde böyle bir yapı yoktur: orada tutulan tek şey bir önceki ağaçtır ve ilişkiler her karşılaştırmada yeniden türetilir. Burada ilişkiler kalıcıdır ve bellekte yer kaplar.

Güncelleme satırındaki 202 abonelik ikinci bedeli gösteriyor. Bir hesap her çalıştığında bağımlılıklarını yeniden kaydeder; koşullu bir okuma bir sonraki çalışmada başka bir kaynağa bağlanabileceği için kayıt her seferinde tazelenmek zorundadır. Özet hesabı iki yüz sinyali yeniden okuyup yeniden abone oluyor. Defterin bakımı da bir iş kalemidir.

Üçüncü bedel görünmez bağlantıdır. Bir değerin nerede okunduğu kaynak metinde işaretlenmemiştir; okumanın kendisi kayıt yaptırır. Bir okumayı yanlış yere koymak — örneğin bir etkinin dışına çıkarmak — bağlantıyı sessizce koparır ve görünüm güncellenmez. Konum tabanlı kancalardaki çağrı sırası kuralı gibi, bu da dilin denetlemediği bir sözleşmedir.

Elmas Bağımlılık ve Tutarlılık

Son iki bölüm, bildirim tabanlı bir sistemin en ince sorununu gösteriyor.

Ham ölçümden iki değer türetiliyor — santigrat ve fahrenhayt — ve üçüncü bir değer ikisini birleştiriyor. Ham değer yazıldığında iki türetilmiş değer de geçersizleşir, ama sırayla güncellenirler. Yayılım anlık ve derinlemesine yapılırsa, santigrat güncellendiği anda özet de güncellenir; oysa fahrenhayt hâlâ eski değerindedir.

Günlükteki ikinci satır bunu gösteriyor: 10.0 °C / 24.4 °F. Bu değer hiçbir zaman gerçek olmadı — santigrat yeni, fahrenhayt eski. Buna tutarsız ara değer (glitch) denir ve etkiyi bir kez fazladan çalıştırmakla kalmaz, o çalıştırmada yanlış bir görüntü üretir.

Seviye sıralı yayılımda bu olmuyor. Her hesaba, kaynaklarının en büyük seviyesinden bir fazla seviye verilir; güncelleme kuyruğu seviye sırasıyla boşaltılır. Böylece bir hesap, bütün kaynakları güncellendikten sonra çalışır. Günlükte yalnızca başlangıç değeri ve tutarlı son değer var.

Bu, ailenin gerçekleştirim ayrıntısı gibi görünen ama doğrudan doğruluk taşıyan bir özelliğidir. Bir çerçevenin tepkisel çekirdeği değerlendirilirken sorulacak sorulardan biri, tutarsız ara değerlerin tanım gereği engellenip engellenmediğidir.

Toplu Yazma

İkinci bölüm ikinci bir tutarlılık aracını gösteriyor. İki sinyale ayrı ayrı yazıldığında özet hesabı iki kez çalışıyor ve etkiler dört kez tetikleniyor. Aynı iki yazma tek bir toplu işlemde yapıldığında özet bir kez hesaplanıyor, etki sayısı üçe iniyor.

Kazanç yalnızca iş miktarı değildir. Toplu yazma, iki değişikliğin tek bir geçiş olarak görünmesini sağlar; arada oluşan ve hiçbir zaman geçerli olmayan durum dışarıya sızmaz. Bileşen Yaşam Döngüsü ve Durum konusundaki toplu işleme kavramının aynısıdır; burada bileşen sınırına değil, açıkça işaretlenen bir bloğa bağlıdır.

Ailenin Profili

Güncelleme birimi bağlantıdır; ince tanelilik tek bir metin düğümüne kadar iner. İş miktarı üretilen ağacın büyüklüğüyle değil değişen bağlantı sayısıyla orantılıdır ve belleme çoğu durumda gerekmez — türetilmiş değer zaten bellenmiştir.

Karşılığında çalışma zamanı, bağımlılık çizgesini kurmak ve bakımını yapmak zorundadır: kurulum daha pahalıdır, bellek kullanımı bağlantı sayısıyla artar ve her hesap çalıştığında kayıtlar tazelenir. Bileşen işlevinin bir kez çalışması, “her çizimde baştan” alışkanlığına göre farklı bir zihinsel model ister; kurulumda okunan bir değer donar, izlenmesi için tepkisel olarak okunması gerekir.

Özet

  • Bu ailede bağımlılık okuma anında kaydedilir; güncelleme birimi bileşen değil tek bir bağlantıdır.
  • Aynı tek hücrelik değişiklik, sanal ağaç ailesinde 802 bileşen çağrısı ve 1004 düğüm karşılaştırması üretirken burada bir türetilmiş hesap ve iki belge yazması üretti.
  • Toplayan hesaplar ince tanelilik dışında kalır: bütün sinyallere bağlı bir türetilmiş değer, hangi sinyal değişirse değişsin yeniden hesaplanır.
  • Kayıt defteri bellekte tutulur ve bakım ister; her hesap çalıştığında bağımlılıklarını yeniden kaydeder.
  • Anlık ve derinlemesine yayılım elmas bağımlılıkta tutarsız ara değer üretir; seviye sıralı yayılım bunu tanım gereği engeller.
  • Toplu yazma hem iş miktarını azaltır hem de ara durumun dışarı sızmasını önler.

Sonraki Adım

İki aile de defterini çalışma zamanında tutuyor: biri ağacı karşılaştırarak, diğeri abonelikleri kaydederek. İkisinin de indirilen kodda bir karşılığı var — fark alma algoritması ya da bağımlılık çizgesi düzeneği, sayfa açıldığı anda tarayıcıya inmek zorunda. Oysa hangi ifadenin hangi değere bağlı olduğu, çoğu durumda kaynak metne bakarak da anlaşılabilir. Bir şablonda bir metin düğümünün hangi değişkeni okuduğu yazılıdır. Sonraki ders, bu çıkarımı kod çalışmadan önce yapan yaklaşımı ele alır: bağımlılıkları derleme zamanında çözen ve çalışma zamanına yalnızca güncelleme kodunu bırakan bir dönüştürücü yazar.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat