İçeriğe geç
academia.sh

Ders 08 / 21

Katman Önbelleği

Yönerge sırasının derleme işine etkisi ölçülür: katman anahtarı önceki katmanın anahtarıyla yönergenin girdi özetinden türetilir, iki yönerge sırası aynı sekiz derlemelik değişiklik dizisinde koşturulur ve önbellek isabet oranı, yeniden üretilen katman sayısı ile yazılan bayt karşılaştırılır.

İçindekiler

Önceki ders her yönergeden bir katman doğduğunu ölçtü ve tek bir calistir satırının imajın büyük bölümünü ürettiğini saydı. O ölçüm tek bir derlemeyi kapsıyordu. Bir servis günde birkaç kez derlenirse aynı satır her seferinde aynı işi yapar — oysa girdisi değişmediyse çıktısı da değişmez ve katmanlar zaten içerik özetiyle adlanır.

Bu ders o tekrarı kaldıran yapıyı kurar. Katman önbelleği, üretilmiş bir katmanı anahtarıyla saklar; sonraki derlemede aynı anahtar çıkarsa katman yeniden üretilmez, saklanandan alınır. Anahtarın neyden türediği bir karar doğurur: yönergelerin sırası. Ölçülecek şey de odur — bağımlılık kurulumunun mu yoksa kaynak kopyalamanın mı önce yazıldığı, aynı değişiklik dizisinde isabet oranını, yeniden üretilen katman sayısını ve yazılan baytı ne kadar oynatır?

IM13. Önbellek node ile kurulan bir modeldir; anahtarlar bir eşlemede tutulur, katmanlar gerçekten onbellek/<anahtar>/ altına yazılır. Ağaç önceki dersin ağacının bu ölçüm için sadeleştirilmiş biçimidir; bayt değerleri o dersin sayılarıyla karşılaştırılmaz. IM14. Katman anahtarı, önceki katmanın anahtarı ile yönergenin girdi özetinden türer. kopyala için girdi özetine kopyalanacak dosyaların içeriği girer; calistir için yalnız satırın kendisi girer. IM15. Süre ölçülmez. Ölçülen nicelikler koşumdan bağımsızdır: yeniden üretilen katman sayısı ve yazılan bayt.

Katman Anahtarı

Önbelleğin çalışması tek kurala bağlıdır: bir katmanın anahtarı, altındaki her şeyi ve kendi girdisini kapsamalıdır. Kapsamazsa değişmiş bir alt katmanın üstündeki katman yanlışlıkla isabet eder ve imaj tutarsız çıkar. Kural, önceki dersin zincir kimliğinin aynısıdır; farkı, zincire yönergenin girdisinin de katılmasıdır.

// olcum-agi/onbellek.mjs — katman onbellekli yorumlayici (model)
import { mkdirSync, writeFileSync, readFileSync, readdirSync, appendFileSync, statSync, rmSync } from "node:fs";
import { createHash } from "node:crypto";

const KOK = "proje";
const ozet = (s) => createHash("sha256").update(s).digest("hex").slice(0, 12);
export const metin = (ad, n) => Array.from({ length: n }, (_, i) => `// ${ad} ${i}\n`).join("");

const AGAC = {
  "bagimlilik-listesi": "csv 2.4\nkuyruk 1.9\n",
  "kaynak/giris.js": metin("giris", 20),
  "kaynak/topla.js": metin("topla", 140),
  "kaynak/kuyruk.js": metin("kuyruk", 95),
  "kaynak/sinama/topla.sinama.js": metin("sinama", 210),
};
export const URETIM = {
  "kosum-tabani": { "kok/kosum.js": metin("kosum", 420), "kok/kabuk.js": metin("kabuk", 60) },
  "bagimlilik-kur": { "bagimlilik/csv.js": metin("csv", 900), "bagimlilik/kuyruk.js": metin("kuyruk-bag", 1400) },
};

export const SIRA_A = `temel     kosum-tabani
kopyala   bagimlilik-listesi  /uygulama/liste
calistir  bagimlilik-kur
kopyala   kaynak              /uygulama/kaynak`;

export const SIRA_B = `temel     kosum-tabani
kopyala   kaynak              /uygulama/kaynak
kopyala   bagimlilik-listesi  /uygulama/liste
calistir  bagimlilik-kur`;

// Degisiklik dizisi: her derlemeden once dokunulan dosya, bos ise degisiklik yok.
export const DEGISIKLIK = ["", "kaynak/topla.js", "kaynak/giris.js", "", "bagimlilik-listesi",
  "kaynak/topla.js", "kaynak/sinama/topla.sinama.js", "kaynak/kuyruk.js"];

const yazDosya = (yol, icerik) => {
  mkdirSync(yol.split("/").slice(0, -1).join("/"), { recursive: true });
  writeFileSync(yol, icerik);
};
export const kurAgac = () => {
  rmSync(KOK, { recursive: true, force: true });
  rmSync("onbellek", { recursive: true, force: true });
  for (const [y, i] of Object.entries(AGAC)) yazDosya(`${KOK}/${y}`, i);
};
export const degistir = (yol, no) => appendFileSync(`${KOK}/${yol}`, `// duzeltme ${no}\n`);

const gez = (alt = "") => readdirSync(`${KOK}/${alt}`, { withFileTypes: true }).flatMap((g) =>
  g.isDirectory() ? gez(`${alt}/${g.name}`) : [`${alt}/${g.name}`.replace(/^\//, "")]).sort();
const yollari = (kaynak) => statSync(`${KOK}/${kaynak}`).isDirectory() ? gez(kaynak) : [kaynak];

// Katman anahtari: onceki katmanin anahtari + yonergenin girdi ozeti.
// kopyala icin girdi ozetine kopyalanacak dosyalarin icerigi de girer; calistir icin yalniz satir.
const girdiOzeti = (satir) => {
  const [yonerge, kaynak] = satir.trim().split(/\s+/);
  if (yonerge !== "kopyala") return ozet(satir);
  return ozet(satir + yollari(kaynak).map((y) => y + readFileSync(`${KOK}/${y}`)).join());
};

export const derle = (tanim, onbellek) => {
  let anahtar = "";
  const sonuc = { isabet: 0, uretim: 0, bayt: 0, zincir: [] };
  for (const satir of tanim.trim().split("\n")) {
    anahtar = ozet(anahtar + girdiOzeti(satir));
    sonuc.zincir.push(anahtar);
    if (onbellek.has(anahtar)) { sonuc.isabet++; continue; }
    const [yonerge, kaynak, hedef] = satir.trim().split(/\s+/);
    const ciftler = yonerge === "kopyala"
      ? yollari(kaynak).map((y) => [`${hedef.slice(1)}/${y}`, readFileSync(`${KOK}/${y}`)])
      : Object.entries(URETIM[kaynak]);
    let bayt = 0;
    for (const [y, icerik] of ciftler) { yazDosya(`onbellek/${anahtar}/${y}`, icerik); bayt += icerik.length; }
    onbellek.set(anahtar, bayt);
    sonuc.uretim++; sonuc.bayt += bayt;
  }
  return sonuc;
};

Anahtarın zincirlenmesinin doğrudan sonucu şudur: bir yönerge ıskaladığında üstündeki her yönerge de ıskalar, girdisi hiç değişmemiş olsa bile. Önbellek yukarıdan aşağı değil, aşağıdan yukarı çalışır ve ilk ıskalama noktasından sonrası tümüyle yeniden üretilir.

İki Sıra, Aynı Değişiklik Dizisi

İki tanım dosyası aynı dört yönergeyi taşır, yalnız sıraları farklıdır. Sıra A bağımlılık listesini kopyalayıp kurulumu yapar, kaynağı en sona bırakır; sıra B kaynağı en başa alır. Her iki tanım da aynı sekiz derlemelik dizide koşturulur; dizinin her adımı bir dosyaya tek satır ekler, iki koşumda da birebir aynı sırayla.

IM16. Değişiklik dizisi kurgudaki okuma toplayıcı servisinin bir haftalık düzeltme akışını temsil eder: soğuk önbellekle ilk derleme, beş kaynak değişikliği, bir bağımlılık listesi değişikliği ve hiçbir şeyin değişmediği bir derleme. IM17. Her sıra kendi boş önbelleğiyle başlar; önbellek koşumlar arasında paylaşılmaz.

// olcum-agi/kosum.mjs — iki yonerge sirasini ayni degisiklik dizisinde kosturur
import { kurAgac, degistir, derle, DEGISIKLIK, SIRA_A, SIRA_B } from "./onbellek.mjs";

const kosumYap = (ad, tanim) => {
  kurAgac();
  const onbellek = new Map();
  const toplam = { isabet: 0, uretim: 0, bayt: 0 };
  console.log(`\n${ad}`);
  console.log(" no  degisiklik".padEnd(38) + "isabet  uretim     bayt");
  DEGISIKLIK.forEach((d, i) => {
    if (d) degistir(d, i);
    const s = derle(tanim, onbellek);
    for (const k of Object.keys(toplam)) toplam[k] += s[k];
    console.log(`  ${i + 1}  ${d || (i ? "(degisiklik yok)" : "(ilk derleme)")}`.padEnd(38) +
      String(s.isabet).padStart(4) + String(s.uretim).padStart(8) + String(s.bayt).padStart(9));
  });
  const deneme = DEGISIKLIK.length * tanim.trim().split("\n").length;
  console.log(`  toplam: ${toplam.isabet}/${deneme} isabet (%${((toplam.isabet * 100) / deneme).toFixed(1)}), ` +
    `uretilen katman ${toplam.uretim}, yazilan ${toplam.bayt} bayt, onbellekte ${onbellek.size} girdi ` +
    `${[...onbellek.values()].reduce((a, b) => a + b, 0)} bayt`);
  return toplam;
};

const a = kosumYap("A — bagimlilik once, kaynak sonra", SIRA_A);
const b = kosumYap("B — kaynak once, bagimlilik sonra", SIRA_B);
console.log(`\nB'nin A'ya gore fazladan urettigi katman: ${b.uretim - a.uretim}`);
console.log(`B'nin A'ya gore fazladan yazdigi bayt   : ${b.bayt - a.bayt} (%${(((b.bayt - a.bayt) * 100) / a.bayt).toFixed(1)} daha fazla is)`);
A — bagimlilik once, kaynak sonra
 no  degisiklik                       isabet  uretim     bayt
  1  (ilk derleme)                       0       4    47354
  2  kaynak/topla.js                     3       1     6009
  3  kaynak/giris.js                     3       1     6023
  4  (degisiklik yok)                    4       0        0
  5  bagimlilik-listesi                  1       3    41336
  6  kaynak/topla.js                     3       1     6037
  7  kaynak/sinama/topla.sinama.js       3       1     6051
  8  kaynak/kuyruk.js                    3       1     6065
  toplam: 20/32 isabet (%62.5), uretilen katman 12, yazilan 118875 bayt, onbellekte 12 girdi 118875 bayt

B — kaynak once, bagimlilik sonra
 no  degisiklik                       isabet  uretim     bayt
  1  (ilk derleme)                       0       4    47354
  2  kaynak/topla.js                     1       3    41308
  3  kaynak/giris.js                     1       3    41322
  4  (degisiklik yok)                    4       0        0
  5  bagimlilik-listesi                  2       2    35313
  6  kaynak/topla.js                     1       3    41350
  7  kaynak/sinama/topla.sinama.js       1       3    41364
  8  kaynak/kuyruk.js                    1       3    41378
  toplam: 11/32 isabet (%34.4), uretilen katman 21, yazilan 289389 bayt, onbellekte 21 girdi 289389 bayt

B'nin A'ya gore fazladan urettigi katman: 9
B'nin A'ya gore fazladan yazdigi bayt   : 170514 (%143.4 daha fazla is)

Sıranın Sayısı

Aynı dört yönerge, aynı sekiz değişiklik, iki farklı sonuç. Sıra A 32 katman denemesinin 20’sinde isabet eder — yüzde 62,5 — ve 12 katman üretir. Sıra B aynı dizide 11 isabet, yüzde 34,4 ve 21 katman verir. Yazılan bayt 118 875’e karşı 289 389’dur: sıra B aynı işi yapmak için yüzde 143,4 daha fazla bayt yazar.

Farkın tamamı beş satırda toplanır — kaynak değişikliği olan derlemelerde. Sıra A’da kaynak kopyalama en üsttedir, altındaki hiçbir katman etkilenmez ve yalnız 6 000 bayt civarında bir katman yeniden üretilir. Sıra B’de kaynak kopyalama en alttadır; kaynağa dokunulduğu anda üstündeki liste kopyalama ve bağımlılık kurulumu da geçersizleşir ve 41 300 bayt yazılır. Yedi kat fark, kaynak dosyalarının küçüklüğünden değil, üstlerinde ne olduğundan gelir.

Beşinci derleme ters yönü gösterir. Bağımlılık listesi değiştiğinde sıra A üç katman üretir (41 336 bayt), sıra B iki katman üretir (35 313 bayt) — bu derlemede B daha ucuzdur. Karar tek bir derlemeye bakarak verilemez; dizinin bileşimine bakarak verilir. Sekiz derlemenin beşinde kaynak, birinde bağımlılık listesi değişmiştir ve gerçek bir servis ağacında oran bundan daha da keskindir. Kural şu biçimde yazılır: yönergeler değişme sıklığına göre sıralanır, en seyrek değişen en alta. Bu, önceki dersin katman sırası kuralının derleme tarafındaki karşılığıdır ve iki kural aynı ölçüden çıkar.

Dördüncü derleme de sayılır: hiçbir şey değişmediğinde iki sıra da 4 isabet ve 0 bayt verir. Önbelleğin kazancı yalnız sıraya değil, değişikliğin varlığına da bağlıdır — değişmeyen bir ağaçta her iki sıra da bedava çalışır.

İsabet oranının kendisi de dikkatle okunur. Payda, katman denemesi sayısıdır: dört yönergelik bir tanımda sekiz derleme 32 deneme eder. Yönerge sayısı artarsa payda büyür ve aynı davranış daha yüksek bir oran gösterebilir; oran tek başına iki farklı tanım dosyasını karşılaştırmaya yetmez. Karar taşıyan iki sayı yeniden üretilen katman ile yazılan bayttır, çünkü ikisi de yapılan işin doğrudan ölçüsüdür ve tanımın satır sayısından bağımsızdır. Bu ölçümde üçü de aynı yönü gösterdiği için sıra kararı tartışmasızdır; göstermedikleri durumda bakılacak sütun bayt sütunudur.

Önbelleğin kendi bedeli son sütundadır. Sıra A’nın önbelleğinde 12 girdi ve 118 875 bayt, sıra B’ninkinde 21 girdi ve 289 389 bayt birikir. Hiçbir girdi silinmez: her ıskalama diske yeni bir katman yazar, eskisi yerinde kalır. Önbellek, derleme süresini bayt karşılığında satın alan bir depodur ve kötü sıra bu depoyu da iki buçuk katına çıkarır.

Deponun nerede durduğu ayrıca sayılır. Katman önbelleği, derlemenin koştuğu makinede birikir; 12 ve 21 girdilik depolar o makinenin durumudur. Aynı tanım dosyası boş önbellekli bir makinede koşturulduğunda her iki sıra da tablonun ilk satırındaki bedeli öder: 4 katman, 47 354 bayt. Sıra kararının kazancı ancak ikinci derlemeden itibaren görünür. Önceki kursta ortam denkliği adıyla ölçülen fark burada yeni bir biçim alır: derlemenin sonucu makineden bağımsızdır, maliyeti değildir. Yalıtım bütçesinin bu derste ödenen kalemi budur — tekrarlanan iş ortamdan çıkarılmadı, bir depoya taşındı ve o depo hâlâ ortamda duruyor.

Önbelleğin Ölçmediği

Anahtar calistir yönergesi için yalnız satırın kendisini kapsar. Satır değişmedikçe katman yeniden üretilmez — kurulacak şeyin dışarıda değişmiş olması anahtara girmez. IM18. Dış dünyanın değişmesi, kurulum tablosundaki içeriğin büyütülmesiyle modellenir; liste dosyasına dokunulmaz.

// olcum-agi/bayat.mjs — onbellek anahtarinin olcmedigi girdi
import { kurAgac, degistir, derle, metin, URETIM, SIRA_A } from "./onbellek.mjs";

kurAgac();
const onbellek = new Map();
const ilk = derle(SIRA_A, onbellek);
const kurulumAnahtari = ilk.zincir[2];
console.log(`ilk derleme: ${ilk.uretim} katman, ${ilk.bayt} bayt`);

// Dis dunya degisti: kurulacak bagimliligin icerigi buyudu, liste dosyasi ayni kaldi.
URETIM["bagimlilik-kur"]["bagimlilik/csv.js"] = metin("csv", 950);
const guncel = Object.values(URETIM["bagimlilik-kur"]).reduce((t, i) => t + i.length, 0);
const s = derle(SIRA_A, onbellek);
console.log(`\nliste dosyasi ayni, kurulacak icerik degisti:`);
console.log(`  isabet ${s.isabet}, yeniden uretilen katman ${s.uretim}, yazilan ${s.bayt} bayt`);
console.log(`  onbellekteki kurulum katmani ${onbellek.get(kurulumAnahtari)} bayt, guncel icerik ${guncel} bayt`);
console.log(`  fark ${guncel - onbellek.get(kurulumAnahtari)} bayt, ayni anahtar: ${s.zincir[2] === kurulumAnahtari}`);

degistir("bagimlilik-listesi", 9);
const t = derle(SIRA_A, onbellek);
console.log(`\nliste dosyasina tek satir eklenince:`);
console.log(`  isabet ${t.isabet}, yeniden uretilen katman ${t.uretim}, yazilan ${t.bayt} bayt`);
console.log(`  kurulum katmani ${onbellek.get(t.zincir[2])} bayt`);
ilk derleme: 4 katman, 47354 bayt

liste dosyasi ayni, kurulacak icerik degisti:
  isabet 4, yeniden uretilen katman 0, yazilan 0 bayt
  onbellekteki kurulum katmani 35280 bayt, guncel icerik 35830 bayt
  fark 550 bayt, ayni anahtar: true

liste dosyasina tek satir eklenince:
  isabet 1, yeniden uretilen katman 3, yazilan 41858 bayt
  kurulum katmani 35830 bayt

Yalıtımın delindiği yer burasıdır. Kurulacak içerik 550 bayt büyüdüğü hâlde dört yönergenin dördü de isabet eder, sıfır katman üretilir ve imaj eski içeriği taşımayı sürdürür. Anahtar aynıdır, çünkü anahtar calistir satırının metnini ölçer, satırın sonucunu değil. Önbellek doğru çalışmaktadır; ölçtüğü şey eksiktir.

Bu, önceki kursta ölçülen yeniden üretilebilirlik sorununun önbellek tarafındaki biçimidir. Bir calistir satırı çıktısı zamana bağlı bir iş yapıyorsa — sürüm sınırı yazılmamış bir kurulum gibi — aynı satır iki farklı zamanda iki farklı katman üretir, ama önbellek ikisini de aynı anahtarla adlandırır. Çözüm önbellekte değil, yönergededir: calistirın girdisi bir dosyaya yazılırsa, o dosya kopyala ile bağlama girer ve içeriği anahtara katılır. Üçüncü blokta liste dosyasına tek satır eklendiğinde tam olarak bu olur — anahtar değişir, kurulum katmanı yeniden üretilir ve depodaki katman 35 830 bayta güncellenir.

Kopyalama tanesi de bir eksiklik kalemidir. Yedinci derlemede değişen dosya bir sınama dosyasıdır; koşumda hiç çalışmayacak bir dosya, kaynak dizini tek yönergeyle kopyalandığı için 6 051 baytlık katmanı yeniden ürettirir. Önceki dersin ölçtüğü gereksiz dosyalar burada ikinci kez bedel ödetir: önce imaj boyutunda, sonra önbellek isabetinde.

Özet

  • Katman anahtarı, önceki katmanın anahtarı ile yönergenin girdi özetinden türer. Bir yönerge ıskaladığında üstündeki her yönerge de ıskalar; önbellek ilk ıskalama noktasından sonrasını tümüyle yeniden üretir.
  • Aynı dört yönerge, aynı sekiz derleme: bağımlılık kurulumu önce yazıldığında 32 denemenin 20’si isabet etti (yüzde 62,5), 12 katman üretildi, 118 875 bayt yazıldı. Kaynak kopyalama öne alındığında isabet 11’e düştü (yüzde 34,4), katman 21’e, yazılan bayt 289 389’a çıktı — yüzde 143,4 daha fazla iş.
  • Fark kaynak değişikliği olan derlemelerde toplanır: sıra A’da 6 000 bayt civarı, sıra B’de 41 300 bayt civarı. Bağımlılık listesi değiştiğinde yön tersine döner; kararı tek derleme değil, değişiklik dizisinin bileşimi verir.
  • Önbelleğin kendi bedeli bir depodur ve yalnız büyür: sıra A 12 girdi ve 118 875 bayt, sıra B 21 girdi ve 289 389 bayt biriktirdi.
  • Anahtar calistir için yalnız satırın metnini ölçer. Kurulacak içerik 550 bayt büyüdüğü hâlde dört yönerge de isabet etti ve sıfır katman üretildi; imaj eski içeriği taşımayı sürdürdü. Girdi bir dosyaya yazılıp bağlama alındığında anahtar değişir ve katman güncellenir.

Sonraki Adım

Önbellek tekrarlanan işi kaldırdı ama imajın içeriğine hiç dokunmadı. Sekiz derlemenin sonunda ortaya çıkan imaj, ilk derlemenin ürettiği yığının aynısıdır: bağımlılık kurulumunun bıraktığı her şey, kopyalanan her kaynak dosyası ve kurulum sırasında kullanılan her araç son katmanda durur. Oysa bunların bir bölümü yalnız derleme sırasında gerekliydi; koşum anında çalışan tek şey uygulamanın kendisidir. Sonraki ders bu ayrımı ölçer: aynı uygulama tek aşamada ve çok aşamada derlendiğinde son imajın boyutu, katman sayısı ve içinde kalan gereksiz dosya sayısı ne kadar ayrılır, ve çok aşamalı derlemenin önbellek tarafındaki bedeli nedir?

İ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