Ders 05 / 21
Önbellek ve Paralellik
Hattın kendi süresi ölçülür: aşama önbelleği gerçek dosyaların içerik özetinden anahtar üretir, on iki koşumluk bir dizide önbellekli ve önbelleksiz süre, isabet oranı ve isabetin değişiklik türüne göre dağılımı sayılır, ardından paralellik uygulanıp kritik yolun koyduğu sınır gösterilir.
İçindekiler
Önceki ders hattın ürettiği çıktıyı sürümledi ve sakladı: her koşumun çıktısı kendi kimliğiyle duruyor, hangi değişiklikten doğduğu okunabiliyor. Bir şey hiç sayılmadı — o çıktının kaç dakikada üretildiği. Hat her koşumda bütün aşamaları baştan koşturuyor, oysa aşamaların çoğunun girdisi iki koşum arasında hiç değişmiyor.
Bu ders hattın kendi süresini ölçer. Elde iki kaldıraç var: aynı girdiyle aynı işi yapan aşamayı bir daha koşturmamak — aşama önbelleği — ve birbirine bağlı olmayan aşamaları aynı anda koşturmak — paralellik. İkisinin de sınırı vardır ve sınırlar aynı yerde durmaz. Kapı ölçüsü burada iki uçludur: bir aşama önbellekten geldiğinde o kapı hiç koşmaz, ve kapının kaçırdığı tam olarak orada doğar.
HT25. Hat node ile kurulan bir modeldir. Aşama süreleri modelin bildirdiği nominal
değerlerdir, koşum makinesinden ölçülmez; ortamdan bağımsız nicelikler koşan aşama sayısı, isabet
sayısı ve önbellek girdisidir. HT26. Aşama anahtarı gerçek dosyalardan hesaplanır: dosyalar
diske yazılır, okunur, özetlenir. HT27. Değişiklik dizisi kurgu bölgesel ölçüm ağının iki
haftalık düzeltme akışını temsil eder; rastgelelik yoktur, dizi elle yazılmıştır. HT28. Kusur
kümesi sekiz kusurdur ve her kusurun hangi kapılarda yakalanabileceği bildirilmiştir; koşan kapı
kendi kusurunu yakalar, koşmayan kapı yakalamaz. HT29. Önbellek girdileri silinmez, saklama
kuralı uygulanmaz. HT30. Çalıştırıcılar özdeştir ve kuyrukta bekleme sıfırdır — o kalem sonraki
derste ölçülür.
İmaj katmanlarının önbelleği bu kursun dışında ölçüldü ve tekrarlanmaz; burada ölçülen birim hat aşamasıdır — kendi girdisi, kendi süresi ve kendi çıktısı olan bir iş adımı.
Aşama Anahtarı
Önbelleğin tek kuralı vardır: bir aşamanın anahtarı, o aşamanın sonucunu değiştirebilecek her şeyi kapsamalıdır. Kapsanmayan bir girdi değiştiğinde anahtar aynı kalır, aşama koşmaz ve hat eski sonucu doğru sanır. Modelde her aşama girdilerini kendisi bildirir.
// olcum-agi/hat.mjs — hat asamalari ve asama onbellegi (model) import { mkdirSync, writeFileSync, readFileSync, readdirSync, statSync, appendFileSync, rmSync } from "node:fs"; import { createHash } from "node:crypto"; const KOK = "proje"; const ozet = (s) => createHash("sha256").update(s).digest("hex").slice(0, 12); const metin = (ad, n) => Array.from({ length: n }, (_, i) => `// ${ad} ${i}\n`).join(""); const AGAC = { "bagimlilik-listesi": "olcum-istemcisi 3.1\nkuyruk-baglayici 1.9\ncsv-cozumleyici 2.4\n", "kaynak/toplayici.js": metin("toplayici", 260), "kaynak/dogrulayici.js": metin("dogrulayici", 180), "kaynak/faturalama.js": metin("faturalama", 240), "sinama/birim/toplayici.js": metin("birim", 300), "sinama/tumlesim/akis.js": metin("tumlesim", 220), "sinama/sozlesme/api.js": metin("sozlesme", 90), "yapilandirma/ortam.json": '{"zaman-asimi":30}\n', "kurallar/statik.json": '{"kural-sayisi":48}\n', "sozlesmeler/api.json": '{"alan-sayisi":17}\n', }; // sure: bildirilmis nominal asama suresi (saniye). taban: onbellege yazilan cikti (bayt). // girdi: asamanin BILDIRDIGI girdiler — kurallar/ ve sozlesmeler/ hicbir asamada bildirilmemistir. export const ASAMALAR = [ { ad: "bagimlilik-cozumu", girdi: ["bagimlilik-listesi"], onceki: [], sure: 42, taban: 41e6 }, { ad: "derleme", girdi: ["kaynak"], onceki: ["bagimlilik-cozumu"], sure: 65, taban: 8.6e6 }, { ad: "statik-cozumleme", girdi: ["kaynak"], onceki: ["bagimlilik-cozumu"], sure: 27, taban: 12e4 }, { ad: "birim-sinamasi", girdi: ["sinama/birim"], onceki: ["derleme"], sure: 38, taban: 9e4 }, { ad: "tumlesim-sinamasi", girdi: ["sinama/tumlesim", "yapilandirma"], onceki: ["derleme"], sure: 96, taban: 26e4 }, { ad: "paketleme", girdi: ["yapilandirma"], onceki: ["derleme"], sure: 18, taban: 9.1e6 }, { ad: "sozlesme-sinamasi", girdi: ["sinama/sozlesme"], onceki: ["paketleme"], sure: 31, taban: 6e4 }, { ad: "imaj-uretimi", girdi: [], onceki: ["paketleme"], sure: 24, taban: 12.3e6 }, ]; export const GERI_YUKLEME = 2; // her isabette ciktinin onbellekten geri yuklenme suresi (saniye) export const SERI = ASAMALAR.reduce((t, a) => t + a.sure, 0); export const kurAgac = () => { rmSync(KOK, { recursive: true, force: true }); for (const [y, i] of Object.entries(AGAC)) { mkdirSync(`${KOK}/${y}`.split("/").slice(0, -1).join("/"), { recursive: true }); writeFileSync(`${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}`])).sort(); const yollari = (g) => (statSync(`${KOK}/${g}`).isDirectory() ? gez(g) : [g]); // Asama anahtari: asama adi + bildirilen girdilerin gercek icerigi + onceki asamalarin anahtarlari. export const kosturHat = (onbellek) => { const anahtar = {}, sonuc = { isabet: [], kosan: [], sure: 0 }; for (const a of ASAMALAR) { const dosyalar = a.girdi.flatMap(yollari); anahtar[a.ad] = ozet(a.ad + dosyalar.map((y) => y + readFileSync(`${KOK}/${y}`)).join() + a.onceki.map((o) => anahtar[o]).join()); if (onbellek.has(anahtar[a.ad])) { sonuc.isabet.push(a.ad); sonuc.sure += GERI_YUKLEME; continue; } onbellek.set(anahtar[a.ad], a.taban + dosyalar.reduce((t, y) => t + statSync(`${KOK}/${y}`).size, 0)); sonuc.kosan.push(a.ad); sonuc.sure += a.sure; } return sonuc; };
Anahtara önceki aşamaların anahtarlarının girmesi bir zincir kurar: bir aşama ıskaladığında ona
bağlı her aşama da ıskalar, kendi girdisi değişmemiş olsa bile. Bu önbelleğin doğru tarafıdır;
yanlış tarafı bildirim listesindedir — kurallar/ ve sozlesmeler/ hattın gerçek girdileridir ama
hiçbir aşama onları bildirmez.
On İki Koşum
Dizi on iki koşumda kurgu ağın bir düzeltme akışını yürütür: kaynak ve sınama değişiklikleri, bir bağımlılık listesi, bir yapılandırma, hiçbir şeyin değişmediği bir koşum ve bildirilmemiş dizinlere dokunan iki değişiklik. Sekiz kusur diziye serpiştirilmiştir.
// olcum-agi/kosum.mjs — on iki koşumluk degisiklik dizisi, onbellekli ve onbelleksiz import { kurAgac, degistir, kosturHat, ASAMALAR, SERI } from "./hat.mjs"; // Kurgu iki haftalik dizi: dokunulan dosya, degisiklik turu, tasidigi kusur, kusuru // yakalayabilecek kapilar. Rastgelelik yok; dizi elle yazilmistir. const DIZI = [ ["", "ilk kosum"], ["kaynak/toplayici.js", "kaynak", "K1", ["birim-sinamasi", "tumlesim-sinamasi"]], ["kaynak/dogrulayici.js", "kaynak", "K2", ["statik-cozumleme"]], ["sinama/birim/toplayici.js", "sinama"], ["", "degisiklik yok"], ["bagimlilik-listesi", "bagimlilik", "K3", ["tumlesim-sinamasi"]], ["kurallar/statik.json", "kural", "K4", ["statik-cozumleme"]], ["kaynak/faturalama.js", "kaynak", "K5", ["birim-sinamasi", "statik-cozumleme"]], ["yapilandirma/ortam.json", "yapilandirma"], ["sozlesmeler/api.json", "sozlesme", "K6", ["sozlesme-sinamasi"]], ["sinama/tumlesim/akis.js", "sinama", "K7", ["tumlesim-sinamasi"]], ["kaynak/toplayici.js", "kaynak", "K8", ["birim-sinamasi"]], ]; kurAgac(); const onbellek = new Map(), tur = {}, kacan = []; const kapi = Object.fromEntries(ASAMALAR.map((a) => [a.ad, { kosum: 0, sure: 0, tuttu: 0, yalniz: 0 }])); let topIsabet = 0, topSure = 0; console.log(" no degisiklik".padEnd(32) + "tur".padEnd(15) + "isabet kosan sure(s)"); DIZI.forEach(([dosya, t, kusur, yakalayan], i) => { if (dosya) degistir(dosya, i); const s = kosturHat(onbellek); topIsabet += s.isabet.length; topSure += s.sure; tur[t] = tur[t] || { deneme: 0, isabet: 0 }; tur[t].deneme += ASAMALAR.length; tur[t].isabet += s.isabet.length; for (const ad of s.kosan) { kapi[ad].kosum++; kapi[ad].sure += ASAMALAR.find((a) => a.ad === ad).sure; } if (kusur) { const tutan = yakalayan.filter((y) => s.kosan.includes(y)); if (!tutan.length) kacan.push(kusur); else for (const y of tutan) { kapi[y].tuttu++; if (tutan.length === 1) kapi[y].yalniz++; } } console.log(` ${String(i + 1).padStart(2)} ${dosya || "(" + t + ")"}`.padEnd(32) + t.padEnd(15) + String(s.isabet.length).padStart(4) + String(s.kosan.length).padStart(6) + String(s.sure).padStart(7)); }); const deneme = DIZI.length * ASAMALAR.length, onbelleksiz = DIZI.length * SERI; console.log(`\nonbelleksiz ${onbelleksiz} s, onbellekli ${topSure} s (%${(100 - (topSure * 100) / onbelleksiz).toFixed(1)} kisalma); ` + `isabet ${topIsabet}/${deneme} (%${((topIsabet * 100) / deneme).toFixed(1)}); ` + `onbellek ${onbellek.size} girdi ${([...onbellek.values()].reduce((a, b) => a + b, 0) / 1e6).toFixed(1)} MB`); console.log("\nisabet / degisiklik turu" + " ".repeat(9) + "kapi kosum sure tuttugu yalniz"); const satir = Object.entries(tur).map(([t, d]) => ` ${t.padEnd(15)}${d.isabet}/${d.deneme} %${((d.isabet * 100) / d.deneme).toFixed(0)}`); ASAMALAR.forEach((a, i) => { const k = kapi[a.ad]; console.log((satir[i] || "").padEnd(33) + a.ad.padEnd(19) + String(k.kosum).padStart(3) + String(k.sure + "s").padStart(7) + String(k.tuttu).padStart(7) + String(k.yalniz).padStart(8)); }); console.log(`\nkusur kumesi 8, kacan ${kacan.length}: ${kacan.join(", ")} — bildirilmemis girdi, bayat isabet`);
no degisiklik tur isabet kosan sure(s) 1 (ilk kosum) ilk kosum 0 8 341 2 kaynak/toplayici.js kaynak 1 7 301 3 kaynak/dogrulayici.js kaynak 1 7 301 4 sinama/birim/toplayici.js sinama 7 1 52 5 (degisiklik yok) degisiklik yok 8 0 16 6 bagimlilik-listesi bagimlilik 0 8 341 7 kurallar/statik.json kural 8 0 16 8 kaynak/faturalama.js kaynak 1 7 301 9 yapilandirma/ortam.json yapilandirma 4 4 177 10 sozlesmeler/api.json sozlesme 8 0 16 11 sinama/tumlesim/akis.js sinama 7 1 110 12 kaynak/toplayici.js kaynak 1 7 301 onbelleksiz 4092 s, onbellekli 2273 s (%44.5 kisalma); isabet 46/96 (%47.9); onbellek 50 girdi 287.5 MB isabet / degisiklik turu kapi kosum sure tuttugu yalniz ilk kosum 0/8 %0 bagimlilik-cozumu 2 84s 0 0 kaynak 4/32 %13 derleme 6 390s 0 0 sinama 14/16 %88 statik-cozumleme 6 162s 2 1 degisiklik yok 8/8 %100 birim-sinamasi 7 266s 3 1 bagimlilik 0/8 %0 tumlesim-sinamasi 8 768s 3 2 kural 8/8 %100 paketleme 7 126s 0 0 yapilandirma 4/8 %50 sozlesme-sinamasi 7 217s 0 0 sozlesme 8/8 %100 imaj-uretimi 7 168s 0 0 kusur kumesi 8, kacan 2: K4, K6 — bildirilmemis girdi, bayat isabet
İsabetin Okunması
Toplam süre 4 092 saniyeden 2 273 saniyeye iner: yüzde 44,5 kısalma, 96 aşama denemesinde 46 isabet. Bu tek sayı hiçbir karar taşımaz; kararı türe göre dağılım verir.
Sınama değişikliklerinde isabet yüzde 88’dir — bir sınama dosyasına dokunmak yalnız o sınama aşamasını koşturur. Kaynak değişikliklerinde isabet yüzde 13’e düşer: kaynak zincirin dibindedir, ona dokunulduğunda yedi aşama yeniden koşar. Bağımlılık listesi değişikliğinde isabet sıfırdır; o dosya en alttaki aşamanın girdisidir ve hattın tamamını geçersizleştirir. Kazanç hattın hangi katmanına ne sıklıkla dokunulduğuna bağlıdır.
Yüzde 100 gösteren üç satırdan yalnız biri doğrudur. Hiçbir şeyin değişmediği koşumda sekiz
aşamanın sekizi isabet eder ve hat 16 saniyede biter; önbelleğin yapması gereken tam olarak budur.
Kural ve sözleşme değişikliklerinde de sekiz isabet görünür — ama orada bir şey değişmiştir.
kurallar/statik.json statik çözümlemenin kural kümesi, sozlesmeler/api.json sözleşme
sınamasının ölçütüdür; ikisi de hiçbir aşamanın bildirdiği girdi değildir. Anahtar değişmez, kapı
koşmaz, hat yeşil biter. En yüksek isabet oranını veren iki değişiklik türü, önbelleğin
görmediği türdür.
Kapı hesabı bunun bedelini yazar. Sekiz kusurdan altısı yakalanır, ikisi kaçar: K4 kural kümesinin sıkılaştırılmasıyla görünür hâle gelen kusurdur, K6 sözleşme alanının değişmesinden doğar; ikisi de yakalanabilecekleri kapı önbellekten geldiği için geçer. Marjinal değer sütunu kapıları ayırır: tümleşim sınaması sekiz koşumda 768 saniye harcar, üç kusur tutar ve bunların ikisini yalnız kendisi yakalar — hattın en pahalı ve en gerekli kapısıdır. Birim sınaması 266 saniyede üç kusur tutar ama yalnız birini tek başına; öteki ikisini tümleşim sınaması da yakalardı. Dört aşama — bağımlılık çözümü, derleme, paketleme, imaj üretimi — hiç kusur tutmaz; onlar kapı değil üretim adımıdır ve bu hesaba girmez.
Bildirim düzeltilirse bedel tablodan okunur: yedinci koşumda statik çözümleme 2 saniyelik isabet yerine 27 saniye, onuncu koşumda sözleşme sınaması 2 yerine 31 saniye alır. Toplam 54 saniye, önbellekli sürenin yüzde 2,4’ü — karşılığında iki kusur kaçmaz.
Önbelleğin kendi bedeli üç kalemdir. Depo: on iki koşumda 50 girdi ve 287,5 MB birikir, hiçbiri silinmez. Geri yükleme: 46 isabetin her biri 2 saniye ödetir, toplam 92 saniye. Üçüncüsü sayıya gelmez ve en pahalısıdır — yukarıdaki iki kaçan kusur, geçersizleşme hatasının faturasıdır.
Kritik Yol
Önbellek tekrarlanan işi kaldırdı; kalan iş hâlâ tek sırada koşuyor. Aşamalar bir bağımlılık çizgesi kurar, birbirine bağlı olmayanlar aynı anda koşabilir ve sınırı çizgenin en uzun zinciri belirler.
// olcum-agi/paralel.mjs — asama cizgesi, calistirici sayisi ve kritik yol import { ASAMALAR, GERI_YUKLEME } from "./hat.mjs"; const bul = (ad) => ASAMALAR.find((a) => a.ad === ad); const ardil = (ad) => ASAMALAR.filter((b) => b.onceki.includes(ad)); // Bir asamadan hattin sonuna kalan en uzun sure. Kritik yol, basi en buyuk olan zincirdir. const kalan = (a) => a.sure + Math.max(0, ...ardil(a.ad).map(kalan)); const zincir = (a) => (ardil(a.ad).length ? `${a.ad}(${a.sure}) -> ` + zincir(ardil(a.ad).reduce((x, y) => (kalan(x) >= kalan(y) ? x : y))) : `${a.ad}(${a.sure})`); // Liste cizelgelemesi: oncekileri BITMIS asamalar arasindan kalan yolu en uzun olan, // bosalan calistiriciya verilir. Calistirici sayisi n. const cizelgele = (sure, n) => { const biten = new Set(), suren = [], bekleyen = ASAMALAR.map((a) => a.ad); let saat = 0; while (biten.size < ASAMALAR.length) { const hazir = bekleyen.filter((ad) => bul(ad).onceki.every((o) => biten.has(o))) .sort((x, y) => kalan(bul(y)) - kalan(bul(x))); while (suren.length < n && hazir.length) { const ad = hazir.shift(); bekleyen.splice(bekleyen.indexOf(ad), 1); suren.push({ ad, biter: saat + sure(ad) }); } saat = Math.min(...suren.map((s) => s.biter)); for (let i = suren.length - 1; i >= 0; i--) if (suren[i].biter <= saat) { biten.add(suren[i].ad); suren.splice(i, 1); } } return saat; }; const bas = ASAMALAR.filter((a) => !a.onceki.length).reduce((x, y) => (kalan(x) >= kalan(y) ? x : y)); const sinir = kalan(bas), seri = ASAMALAR.reduce((t, a) => t + a.sure, 0); const soguk = (ad) => bul(ad).sure; // Kaynak degisikliginde onbellek yalniz bagimlilik-cozumunde isabet eder (yukaridaki tablo, 2. kosum). const isabetli = (ad) => (ad === "bagimlilik-cozumu" ? GERI_YUKLEME : bul(ad).sure); console.log(`seri ${seri} s, kritik yol ${sinir} s: ${zincir(bas)}`); console.log("\ncalistirici soguk kritik yola uzaklik ek calistiricinin kazanci onbellekli (kaynak degisikligi)"); let onceki = null; for (let n = 1; n <= 6; n++) { const s = cizelgele(soguk, n); console.log(String(n).padStart(9) + String(s + " s").padStart(9) + String(s - sinir + " s").padStart(18) + (onceki === null ? "-" : onceki - s + " s").padStart(24) + String(cizelgele(isabetli, n) + " s").padStart(28)); onceki = s; }
seri 341 s, kritik yol 203 s: bagimlilik-cozumu(42) -> derleme(65) -> tumlesim-sinamasi(96)
calistirici soguk kritik yola uzaklik ek calistiricinin kazanci onbellekli (kaynak degisikligi)
1 341 s 138 s - 301 s
2 218 s 15 s 123 s 178 s
3 203 s 0 s 15 s 163 s
4 203 s 0 s 0 s 163 s
5 203 s 0 s 0 s 163 s
6 203 s 0 s 0 s 163 s
İkinci çalıştırıcı 123 saniye kazandırır, üçüncüsü 15 saniye, dördüncüsü sıfır. Süre üç çalıştırıcıda düzleşip 203 saniyede durur; bu sayı çalıştırıcı sayısından değil çizgeden gelir. Sınırı koyan zincir bağımlılık çözümü, derleme ve tümleşim sınamasıdır: 42 + 65 + 96. Üçü birbirini beklemek zorundadır, dolayısıyla hiçbir çalıştırıcı sayısı hattı 203 saniyenin altına indiremez. En yüksek hızlanma 1,68 kattır ve dördüncü çalıştırıcıdan sonrası ödenmiş boş kapasitedir.
Sınırı aşmanın tek yolu zinciri kısaltmaktır: tümleşim sınaması iki bağımsız parçaya bölünürse kritik yol 42 + 65 + 48’e iner. Bu bir hat tasarımı kararıdır, çalıştırıcı satın alma kararı değildir.
İki kaldıraç birbirini çarpar ama aynı yerden tutmaz. Son sütun kaynak değişikliği koşumunu gösterir: önbellek zincirin başındaki 42 saniyeyi 2’ye indirir, üç çalıştırıcıyla süre 163 saniyeye oturur. Önbellek zincirin başını kısaltır, paralellik zincirin dışındaki işi kaldırır; ikisi de ortadaki 65 ve 96 saniyeye dokunamaz. Hattın gerçek tabanı orasıdır.
Özet
- Aşama anahtarı, aşamanın bildirdiği girdilerin içeriği ile önceki aşamaların anahtarlarından türer. Bildirilmeyen girdi değiştiğinde anahtar değişmez, kapı koşmaz ve hat yeşil biter.
- Önbellek on iki koşumda süreyi 4 092 saniyeden 2 273 saniyeye indirdi (yüzde 44,5), isabet 96 denemede 46 oldu. Dağılım kararı taşır: sınamada yüzde 88, kaynakta yüzde 13, bağımlılık listesinde sıfır.
- Kural ve sözleşme değişikliklerinde görünen yüzde 100 isabet, önbelleğin körlüğüdür. İki kusur (K4, K6) yakalanabilecekleri kapı önbellekten geldiği için kaçtı; bildirimin düzeltilmesi on iki koşuma 54 saniye, yani yüzde 2,4 ekliyor.
- Kapıların marjinal değeri eşit değildir: tümleşim sınaması 768 saniyede üç kusur tuttu, ikisini yalnız kendisi; birim sınaması 266 saniyede üç kusur tuttu ama yalnız birini tek başına. Önbelleğin bedeli ise 50 girdi, 287,5 MB depo ve 92 saniyelik geri yüklemedir.
- Paralellik hattı 203 saniyenin altına indiremez. Sınırı bağımlılık çözümü → derleme → tümleşim sınaması zinciri koyar; üçüncü çalıştırıcıdan sonra ek çalıştırıcının kazancı sıfırdır.
Sonraki Adım
Bu ölçüm iki şeyi bedava saydı. Kırk altı isabetin her biri, önceki koşumun çıktısını hâlâ bulabilen bir makine varsayar; HT30 de kuyrukta beklemeyi sıfır alıp üç çalıştırıcının her an hazır olduğunu kabul etti. İki varsayım da aynı karara bağlıdır: hattın nerede koştuğu. Hat her koşumda temiz kurulan bir makinede koşarsa önbellek her seferinde boştur ve buradaki 1 819 saniyelik kazanç buharlaşır; kendi tutulan makinelerde koşarsa önbellek kalır, ama makineler bekler, eskir ve bakım ister. Sonraki ders aynı hattı iki çalıştırıcı türünde koşturup dört kalemi sayar — kuyrukta bekleme, koşum başına maliyet, önbelleğin kalıcılığı ve bakım yükü — sonra eşiği hesaplar: günde kaç koşumdan sonra hangisi ucuzluyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.