İçeriğe geç
academia.sh

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/dagitim yerine tüm /is taşı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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat