Ders 09 / 21
Çok Aşamalı Derleme
Derleme araçlarının çıktıdan ayrılması ölçülür: aynı uygulama tek aşamalı ve çok aşamalı olarak derlenir, son imajın boyutu, katman sayısı ve içinde kalan gereksiz dosya sayısı karşılaştırılır, çok aşamalının bedeli de sayılır — üretilen toplam bayt, gönderilmeyen ara katmanlar ve önbellek davranışı.
İçindekiler
Önceki ders tekrarlanan işi kaldıran önbelleği kurdu ve yönerge sırasının bedelini saydı. O ölçüm boyunca imajın içeriğine hiç dokunulmadı: sekiz derlemenin sonunda ortaya çıkan yığın, ilk derlemenin ürettiğinin aynısıydı. Bağımlılık kurulumunun bıraktığı her dosya, kopyalanan her kaynak dosyası ve kurulum sırasında kullanılan her araç son katmanda duruyordu.
Oysa bunların bir bölümü yalnız derleme sırasında gereklidir. Bir derleyici, bir bağlayıcı, sınama koşucusu ve geliştirme bağımlılıkları çıktıyı üretmek için kullanılır; çıktı üretildikten sonra hiçbiri çalıştırılmaz. Çok aşamalı derleme bu ayrımı yönerge düzeyinde kurar: derleme kendi aşamasında yapılır, son imaja yalnız üretilen dosyalar taşınır. Ölçülecek soru bellidir — son imajın boyutu, katman sayısı ve içinde kalan gereksiz dosya sayısı ne kadar ayrılır, ve bu ayrımın bedeli nedir?
IM19. Aşağıdaki yorumlayıcı önceki derslerin modelinin genişletilmiş biçimidir: asama
yönergesi eklenir ve kopyala kaynağı <asama>:<yol> biçiminde yazıldığında önceki bir aşamanın
görünümünden okur. IM20. Derleme tabanı, koşum tabanının üstüne üç derleme aracı ekler; iki
taban arasındaki fark modelde açıkça yazılıdır. IM21. Paketleme modeli sınama dışı kaynak
dosyalarını ve koşum bağımlılıklarını tek dosyada birleştirir; geliştirme bağımlılıkları pakete
girmez. IM22. Koşumda gereken dosyalar elle tanımlanır: koşum tabanı dosyaları ve paketlenmiş
çıktı. Kurgudaki ölçüm ağının okuma toplayıcı servisi ölçülür.
Derleme Zamanı ile Koşum Zamanı
Tek aşamalı tanımda bütün yönergeler tek bir yığına yazılır ve o yığın hem derlemeyi yapar hem de
çalıştırılır. Çok aşamalı tanımda asama yönergesi yığını böler: her aşama kendi tabanından
başlar, kendi zincirini kurar ve son aşama önceki aşamalardan yalnız istediği yolları alır. Son
imaj, son aşamanın katmanlarıdır; önceki aşamaların katmanları imaja hiç girmez.
Ayrımın zincir üzerindeki karşılığı da vardır. Her asama yönergesi anahtar zincirini sıfırlar:
ikinci aşamanın taban katmanı, birinci aşamada ne olduğundan bağımsız bir anahtar alır ve kaynak
değiştiğinde bile önbellekten gelir. İki aşama arasında geçen tek bağ, taşınan dosyaların
içeriğidir. Önceki dersin kuralı burada da geçerlidir — bir katmanın anahtarı altındaki her şeyi
kapsar — ama “altındaki her şey” artık aşama sınırında kesilir. Bu, çok aşamalı derlemenin
önbellek davranışını hem daha iyi hem de daha kötü yapan tek yapısal ayrımdır: ikinci aşamanın
tabanı bedavaya gelir, ama taşınan paket her değiştiğinde kopyalama katmanı yeniden üretilir.
// olcum-agi/asama.mjs — asamali derleme yorumlayicisi ve katman onbellegi (model) import { mkdirSync, writeFileSync, readFileSync, readdirSync, 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 = { "kaynak/giris.js": metin("giris", 22), "kaynak/topla.js": metin("topla", 150), "kaynak/kuyruk.js": metin("kuyruk", 96), "kaynak/sinama/topla.sinama.js": metin("sinama", 205) }; const KOSUM_TABANI = { "kok/kosum.js": metin("kosum", 400), "kok/kabuk.js": metin("kabuk", 55) }; // Derleme tabani, kosum tabaninin ustune derleme araclarini ekler. const DERLEME_TABANI = { ...KOSUM_TABANI, "arac/derleyici.js": metin("derleyici", 3200), "arac/baglayici.js": metin("baglayici", 1450), "arac/basliklar.js": metin("basliklar", 900) }; // Paketleme: sinama disi kaynak ile kosum bagimliliklari tek dosyada birlesir. const paketle = (gorunum) => [...gorunum].filter(([y]) => (y.startsWith("is/kaynak/") && !y.includes("/sinama/")) || y.startsWith("bagimlilik/kosum/")) .sort().map(([, i]) => i).join(""); const URETIM = { "derleme-tabani": () => DERLEME_TABANI, "kosum-tabani": () => KOSUM_TABANI, "bagimlilik-kur": () => ({ "bagimlilik/gelistirme/sinama-kosucu.js": metin("sinama-kosucu", 2100), "bagimlilik/gelistirme/derleme-eklentisi.js": metin("derleme-eklentisi", 1600), "bagimlilik/kosum/csv.js": metin("csv", 780), "bagimlilik/kosum/kuyruk.js": metin("kuyruk-bag", 640) }), "yapiyi-uret": (gorunum) => ({ "is/dagitim/paket.js": paketle(gorunum) }), }; export const TEK = `asama tek temel derleme-tabani kopyala kaynak /is/kaynak calistir bagimlilik-kur calistir yapiyi-uret giris /is/dagitim/paket.js`; export const COK = `asama derleyici temel derleme-tabani kopyala kaynak /is/kaynak calistir bagimlilik-kur calistir yapiyi-uret asama cikti temel kosum-tabani kopyala derleyici:/is/dagitim /uygulama giris /uygulama/paket.js`; // Ayni cok asamali tanim, aradan tum is dizinini tasiyan biciminde. export const COK_GENIS = COK.replace("derleyici:/is/dagitim /uygulama", "derleyici:/is /uygulama") .replace("giris /uygulama/paket.js", "giris /uygulama/dagitim/paket.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("imaj", { 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(); // Kopyalanacak ciftler: agactan ya da baska bir asamanin gorunumunden. const kopyaCiftleri = (kaynak, hedef, asamalar) => { if (!kaynak.includes(":")) return gez(kaynak).map((y) => [`${hedef.slice(1)}/${y.slice(kaynak.length + 1)}`, readFileSync(`${KOK}/${y}`).toString()]); const [ad, yol] = kaynak.split(":"); const onek = `${yol.slice(1)}/`; return [...asamalar.get(ad).gorunum].filter(([y]) => y.startsWith(onek)) .map(([y, i]) => [`${hedef.slice(1)}/${y.slice(onek.length)}`, i]); }; export const derle = (tanim, onbellek = new Map()) => { const asamalar = new Map(); let simdi = null, anahtar = ""; const sayac = { uretim: 0, isabet: 0, bayt: 0 }; for (const ham of tanim.trim().split("\n")) { const [yonerge, kaynak, hedef] = ham.trim().split(/\s+/); if (yonerge === "asama") { simdi = { ad: kaynak, gorunum: new Map(), katman: 0 }; asamalar.set(kaynak, simdi); anahtar = ""; continue; } if (yonerge === "giris") continue; // ustveri: katman uretmez const girdi = yonerge === "kopyala" ? kopyaCiftleri(kaynak, hedef, asamalar).join() : ""; anahtar = ozet(anahtar + ham + girdi); let ciftler; if (onbellek.has(anahtar)) { ciftler = onbellek.get(anahtar); sayac.isabet++; } else { ciftler = yonerge === "kopyala" ? kopyaCiftleri(kaynak, hedef, asamalar) : Object.entries(URETIM[kaynak](simdi.gorunum)); for (const [y, i] of ciftler) yazDosya(`imaj/${simdi.ad}/kat-${simdi.katman}/${y}`, i); onbellek.set(anahtar, ciftler); sayac.uretim++; sayac.bayt += ciftler.reduce((t, [, i]) => t + i.length, 0); } simdi.katman++; for (const [y, i] of ciftler) simdi.gorunum.set(y, i); } return { asamalar, sayac, son: simdi }; }; // Kosumda gereken: kosum tabani dosyalari ve paketlenmis cikti. export const gerekli = (yol) => yol.startsWith("kok/") || yol.endsWith("paket.js");
İki Derleme, Aynı Uygulama
İki tanım da aynı kaynaktan aynı paketi üretir. Ölçülen şey, paketin yanında ne kaldığıdır.
// olcum-agi/olcimaj.mjs — tek asamali ve cok asamali derlemenin son imajlarini karsilastirir import { kurAgac, derle, gerekli, TEK, COK, COK_GENIS } from "./asama.mjs"; const bak = (etiket, tanim) => { kurAgac(); const { asamalar, sayac, son } = derle(tanim); const dosyalar = [...son.gorunum]; const gereksiz = dosyalar.filter(([y]) => !gerekli(y)); const bayt = dosyalar.reduce((t, [, i]) => t + i.length, 0); const ara = [...asamalar.values()].filter((a) => a !== son); console.log(etiket.padEnd(20) + String(son.katman).padStart(4) + String(dosyalar.length).padStart(7) + String(bayt).padStart(9) + String(gereksiz.length).padStart(9) + String(gereksiz.reduce((t, [, i]) => t + i.length, 0)).padStart(9) + String(sayac.bayt).padStart(10) + String(ara.reduce((t, a) => t + a.katman, 0)).padStart(6)); return { bayt, dosyalar, gereksiz }; }; console.log("derleme".padEnd(20) + " kat dosya bayt gereksiz bayt uretilen ara-kat"); const t = bak("tek asamali", TEK); const c = bak("cok asamali", COK); bak("cok asamali, genis", COK_GENIS); console.log(`\nson imaj: ${t.bayt} -> ${c.bayt} bayt, ${(t.bayt / c.bayt).toFixed(1)} kat kucuk`); console.log(`son imajdaki dosya: ${t.dosyalar.length} -> ${c.dosyalar.length}`); console.log(`kosumda gerekmeyen dosya: ${t.gereksiz.length} -> ${c.gereksiz.length}`); console.log("tek asamalidaki gereksiz dosyalar: " + t.gereksiz.map(([y]) => y).join(" "));
derleme kat dosya bayt gereksiz bayt uretilen ara-kat tek asamali 4 14 237174 11 208222 237174 0 cok asamali 2 3 28952 0 0 266126 4 cok asamali, genis 2 7 35044 4 6092 272218 4 son imaj: 237174 -> 28952 bayt, 8.2 kat kucuk son imajdaki dosya: 14 -> 3 kosumda gerekmeyen dosya: 11 -> 0 tek asamalidaki gereksiz dosyalar: arac/derleyici.js arac/baglayici.js arac/basliklar.js is/kaynak/giris.js is/kaynak/kuyruk.js is/kaynak/sinama/topla.sinama.js is/kaynak/topla.js bagimlilik/gelistirme/sinama-kosucu.js bagimlilik/gelistirme/derleme-eklentisi.js bagimlilik/kosum/csv.js bagimlilik/kosum/kuyruk.js
Son imaj 237 174 bayttan 28 952 bayta, dosya sayısı 14’ten 3’e, katman sayısı 4’ten 2’ye iner. Tek aşamalı imajın 11 dosyası ve 208 222 baytı koşumda hiç çalıştırılmaz — imajın yüzde 87,8’i. Bunların arasında üç derleme aracı, dört kaynak dosyası (biri sınama dosyası), iki geliştirme bağımlılığı ve pakete zaten girmiş iki koşum bağımlılığı vardır. Koşum bağımlılıklarının iki kez taşınması ayrıca sayılır: hem paketin içinde hem de yanında dururlar.
Katman sayısının düşmesi de ayrı okunmalıdır. Çok aşamalı imajın iki katmanı vardır çünkü son aşama yalnız iki yönerge çalıştırır; bu, önceki dersin ölçtüğü paylaşım kazancını da değiştirir. Aynı tabandan türeyen başka servisler o iki katmanın birincisini paylaşabilir, ikincisi ise servise özeldir. Tek aşamalı imajda paylaşılabilecek taban, derleme araçlarını da içerdiği için hem daha büyüktür hem de derleme aracı sürümü değiştiğinde geçersizleşir — paylaşımın kazancı değişme sıklığına bağlıdır ve derleme tabanı koşum tabanından daha sık değişir.
Ortamdan çıkarılan fark bu dersin en büyük kalemidir. Derleyici, bağlayıcı ve başlık dosyaları
koşum ortamından tümüyle çıkar; koşan konteynerin dosya sisteminde bir derleme aracı bulunmaz.
Yönerge düzeyinde bunun karşılığı iki satırdır — ikinci bir asama ve aşamalar arası tek bir
kopyala.
Üçüncü satır kazancın nereden geldiğini ayırır. Aynı çok aşamalı tanım, aradan /is/dagitim
yerine tüm /is dizinini taşıdığında son imaj 35 044 bayta ve 7 dosyaya çıkar; dördü
koşumda gerekmeyen kaynak dosyasıdır ve 6 092 bayt tutar. Kazancın tamamı ikinci bir aşama
açmaktan gelmez; taşınan yolun darlığından gelir. İkinci aşama, birinci aşamanın neyini
almayacağına karar verildiği ölçüde işe yarar. Bu, önceki derste ölçülen dizin kopyalama
sorununun aşama sınırındaki biçimidir: kopyala bir yol alır, o yolun altında ne olduğunu
saymaz.
Çok Aşamalının Bedeli
Ayrım bedava değildir. Aynı iki tanım bir önbellekle iki kez koşturulur: ilk derleme soğuk
önbellekle, ikincisi tek bir kaynak dosyasına satır eklendikten sonra. IM23. Değişiklik
kaynak/topla.js dosyasına tek satır ekler. IM24. Aşama görünümü bellekte tutulur,
katmanlar diske yazılır; ölçülen baytlar gerçek dosya baytlarıdır.
// olcum-agi/bedel.mjs — cok asamali derlemenin bedeli: ara katman ve onbellek davranisi import { kurAgac, degistir, derle, TEK, COK } from "./asama.mjs"; const dizi = (etiket, tanim) => { kurAgac(); const onbellek = new Map(); const ilk = derle(tanim, onbellek).sayac; degistir("kaynak/topla.js", 1); const { asamalar, sayac, son } = derle(tanim, onbellek); const ara = [...asamalar.values()].filter((a) => a !== son) .reduce((t, a) => t + [...a.gorunum.values()].reduce((u, i) => u + i.length, 0), 0); const onbellekBayt = [...onbellek.values()].reduce((t, c) => t + c.reduce((u, [, i]) => u + i.length, 0), 0); console.log(etiket.padEnd(14) + String(ilk.uretim).padStart(4) + String(ilk.bayt).padStart(9) + String(sayac.isabet).padStart(8) + String(sayac.uretim).padStart(8) + String(sayac.bayt).padStart(9) + String(onbellek.size).padStart(8) + String(onbellekBayt).padStart(9) + String(ara).padStart(10)); }; console.log("derleme".padEnd(14) + " ilk ilk-bayt isabet uretim bayt onbel. bayt gonderilmez"); dizi("tek asamali", TEK); dizi("cok asamali", COK);
derleme ilk ilk-bayt isabet uretim bayt onbel. bayt gonderilmez tek asamali 4 237174 1 3 134792 7 371966 0 cok asamali 6 266126 2 4 158018 10 424144 237202
Bedel dört kalemdir. Birincisi ilk derlemedir: çok aşamalı düzen 6 katman ve 266 126 bayt üretir, tek aşamalı 4 katman ve 237 174 bayt — fark 28 952 bayt, tam olarak ikinci aşamanın kendi tabanı ve taşınan paket kadar. İkincisi kaynak değişikliğidir: tek aşamalı 3 katman ve 134 792 bayt yeniden üretirken çok aşamalı 4 katman ve 158 018 bayt üretir, çünkü paket değiştiğinde aşamalar arası kopyalama da geçersizleşir. Üçüncüsü önbellektir: 7 girdi ve 371 966 bayta karşı 10 girdi ve 424 144 bayt.
Dördüncü kalem en görünmezidir. Çok aşamalı düzende her derlemede 237 202 bayt üretilir ve hiçbir imaja girmez. Ara aşamanın katmanları gönderilmez ama üretilir; derleyen makinenin diskinde durur, önbellekte yer kaplar ve bir sonraki derlemede yeniden kullanılır. İmaj küçüldü, depo küçülmedi. Yalıtım bütçesi bu dersteki hesabını böyle kapatır: koşum ortamından 208 222 bayt çıkarıldı, karşılığında derleme ortamında 237 202 baytlık kalıcı bir yığın ve derleme başına yüzde 17 daha fazla yazma işi doğdu.
Bu dört kalem karara nasıl çevrilir? İki sayı yan yana konur. Fazladan bedel derleme başına ödenir ve 28 952 bayttır. Kazanç ise imajın her kopyası başına ödenir ve 208 222 bayttır: imaj depoya yazıldığında, bir makineye çekildiğinde ve diskte durduğunda o kadar bayt eksiktir. Oran 7,2’dir — tek bir derlemenin ürettiği tek bir imaj bile fazladan bedeli fazlasıyla karşılar. Kurgu ölçüm ağının dört servisi düşünüldüğünde fark çarpılır, çünkü fazladan bedel her derlemede bir kez, kazanç ise her saklama ve her çekme işleminde yeniden ortaya çıkar. Tek aşamalı düzenin savunulabildiği yer, imajın hiç saklanmadığı ve tek bir makinede üretilip orada çalıştırıldığı durumdur; bunun dışında ölçü tek yönü gösterir.
Aşama sınırının ne olmadığı da yazılmalıdır. kopyala derleyici:/is/dagitim satırı bir kopyalama
sınırıdır, bir doğrulama sınırı değil. Son aşama, önceki aşamanın ürettiği paketi olduğu gibi
alır; içinde ne olduğunu sormaz. Paketleyici sınama dosyalarını dışarıda bırakmasaydı onlar da
son imaja girerdi ve ölçüm bunu yakalamazdı — çünkü gerekli tanımı bu ölçümde elle
yazılmıştır. İmajın kendisi neyin gerektiğini bilmez; taşınan yolun doğru seçildiğini yazan kişi
garanti eder. Önceki derste ölçülen eksikliğin buradaki karşılığı budur: yönerge neyin
gerektiğini değil, neyin kopyalandığını kaydeder.
Özet
- Çok aşamalı derleme, derleme zamanında gereken dosyaları koşum zamanında gerekenlerden ayırır: derleme kendi aşamasında yapılır, son imaja yalnız üretilen dosyalar taşınır.
- Aynı uygulama iki biçimde derlendiğinde son imaj 237 174 bayttan 28 952 bayta indi (8,2 kat), dosya sayısı 14’ten 3’e, katman sayısı 4’ten 2’ye düştü.
- Tek aşamalı imajın 11 dosyası ve 208 222 baytı koşumda hiç çalıştırılmıyordu — imajın yüzde 87,8’i. Üç derleme aracı, dört kaynak dosyası, iki geliştirme bağımlılığı ve pakete zaten girmiş iki koşum bağımlılığı.
- Bedel dört kalemdir: ilk derlemede 28 952 bayt fazla üretim, kaynak değişikliğinde 3 yerine 4 katman ve 134 792 yerine 158 018 bayt, önbellekte 7 yerine 10 girdi, ve her derlemede üretilip hiçbir imaja girmeyen 237 202 bayt. İmaj küçüldü, derleyen makinedeki depo küçülmedi.
- Kazanç ikinci aşamayı açmaktan değil, taşınan yolun darlığından gelir: aradan
/is/dagitimyerine tüm/istaşındığında son imaj 35 044 bayta ve 7 dosyaya çıktı, 4 gereksiz dosya ve 6 092 bayt geri geldi. - Aşama sınırı bir kopyalama sınırıdır, doğrulama sınırı değil. Son aşama önceki aşamanın ürettiğini olduğu gibi alır; koşumda neyin gerektiği tanımı yönergenin dışında, yazan kişinin elindedir.
Sonraki Adım
Son imajın üç dosyası kaldı ve ikisi tabandan geliyor: koşum tabanı 5 740 bayt, paket 23 212 bayt. Derlemenin ürettiği her şey ölçülüp küçültüldükten sonra geriye, imajın üretilmemiş bölümü kalır — altına konan taban. Bu ders boyunca taban bir sabitti; koşum tabanının hangi dosyaları taşıdığı, hangi kabuğu ve hangi kütüphaneleri içerdiği hiç sorulmadı. Oysa taban son imajın beşte birini oluşturuyor ve içeriği yazan kişinin ürettiği bir şey değil, dışarıdan alınan bir şey. Sonraki ders bu seçimi ölçer: küçük bir taban ile geniş bir taban arasındaki fark kaç bayt ve kaç dosyadır, küçük taban neyi eksik bırakır ve o eksiklik hangi durumda ödenir?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.