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
calistiriç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.