İçeriğe geç
academia.sh

Ders 21 / 22

Ölçüm ve Yavaş Komut Analizi

Tek iş parçacıklı bellek içi deponun başarım tanısı: uzun süren tek bir komutun arkasında bekleyen istek sayısının ve toplam gecikmenin sayılması, taramanın imleçle parçalanmasının kazandırdığı ile kaçırdığı giriş, yavaş komut günlüğü eşiğinin taranması.

İçindekiler

Önceki dersin dört bellek hesabı da sessiz bir varsayıma yaslanıyordu: her erişim sırası geldiğinde yapılıyor, hiçbir istek bir başkasını beklemiyor. Bellek içi depoların çoğunda bu varsayım özel bir biçimde kırılır, çünkü komutlar tek bir iş parçacığında sırayla işlenir. Sıra, atomikliği bedava getiren şeydir: iki komut hiçbir zaman iç içe geçmez, kilit gerekmez.

Aynı sıra bir tuzağı da getirir. Bir komut ne kadar sürerse arkasındaki bütün istekler o kadar bekler. Diskteki bir veritabanında yavaş bir sorgu kendi istemcisini geciktirir; burada bütün istemcileri geciktirir. Bu ders o gecikmeyi sayar.

Uzun Komutun Arkasında Bekleyenler

Düzenek tek kuyruklu ve tek iş parçacıklıdır. İstekler her iki iş biriminde bir gelir, sıradan bir komut bir birim sürer; yük yarı doludur (BK15). Bir noktada bütün katalog önbelleğini tarayan bir bakım komutu araya girer ve toplam 4.000 birimlik iş getirir. Aynı iş bir kez tek komut olarak, sonra parçalara bölünmüş komutlar olarak verilir. Toplam iş her satırda aynıdır.

// tek-parcacik.mjs — tek is parcacikli depo: uzun komut arkasinda bekleyen istekler sayilir
const GELIS = 2, ISTEK = 3000, TARAMA_ANI = 2000;  // her 2 birimde bir istek, komut maliyeti 1 birim

function kosum(toplamIs, parca) {                  // tarama isi `parca` komuta bolunur
  const parcaMaliyet = parca ? toplamIs / parca : 0;
  const sira = [];                                 // tek kuyruk, tek is parcacigi, sirayla
  let gelen = 0, kalanParca = parca, taramaGelisi = parca ? TARAMA_ANI : -1;
  let calisan = null, bitisAni = 0;
  let bekleyen = 0, enUzun = 0, toplamBekleme = 0, taramaBitis = 0, islenen = 0;

  for (let t = 0; islenen < ISTEK + parca && t < 200000; t += 1) {
    if (calisan && bitisAni === t) {               // 1) calisan komut bitti
      if (calisan.tarama) { taramaBitis = t; if (kalanParca > 0) taramaGelisi = t; }
      calisan = null; islenen += 1;
    }
    while (gelen < ISTEK && (gelen + 1) * GELIS === t) {             // 2) normal istek gelisi
      sira.push({ gelis: t, maliyet: 1, tarama: false }); gelen += 1;
    }
    if (kalanParca > 0 && taramaGelisi === t) {    // tarama parcasi: ilki takvimle, sonrakiler
      sira.push({ gelis: t, maliyet: parcaMaliyet, tarama: true });  // oncekinin bitisinde
      kalanParca -= 1; taramaGelisi = -1;
    }
    if (calisan === null && sira.length > 0) {     // 3) sirada bekleyen ilk komut alinir
      calisan = sira.shift();
      const b = t - calisan.gelis;
      if (b > 0) { bekleyen += 1; toplamBekleme += b; enUzun = Math.max(enUzun, b); }
      bitisAni = t + calisan.maliyet;
    }
  }
  return { parcaMaliyet, bekleyen, enUzun, toplamBekleme, taramaBitis };
}

console.log(`is yuku: ${ISTEK} istek, ${GELIS} birimde bir gelis, komut maliyeti 1 birim`);
console.log(["tarama", "parca maliyeti", "bekleyen istek", "en uzun bekleme", "toplam bekleme",
  "tarama bitisi"].map((b, i) => (i === 0 ? b.padEnd(18) : b.padStart(16))).join(""));
for (const [ad, toplam, parca] of [
  ["tarama yok", 0, 0], ["tek komut (4000)", 4000, 1],
  ["10 parca", 4000, 10], ["40 parca", 4000, 40], ["200 parca", 4000, 200],
]) {
  const r = kosum(toplam, parca);
  console.log(ad.padEnd(18) + [r.parcaMaliyet, r.bekleyen, r.enUzun, r.toplamBekleme, r.taramaBitis]
    .map((n) => String(n).padStart(16)).join(""));
}
is yuku: 3000 istek, 2 birimde bir gelis, komut maliyeti 1 birim
tarama              parca maliyeti  bekleyen istek en uzun bekleme  toplam bekleme   tarama bitisi
tarama yok                       0               0               0               0               0
tek komut (4000)              4000            2001            3999         5999001            6001
10 parca                       400            2007             786         1045801            8001
40 parca                       100            2022             198          289501            8001
200 parca                       20            2102              38           58681            8001

İlk satır ölçünün sıfır noktasıdır: tarama olmadığında hiçbir istek beklemez, çünkü gelme hızı hizmet hızının yarısıdır. İkinci satırda tek bir komut 4.000 birim çalışır ve arkasında 2.001 istek birikir. En uzun bekleme 3.999 birimdir; yani sıraya taramanın hemen ardından giren istek, kendi işi bir birim sürdüğü hâlde neredeyse dört bin birim bekler. Toplam bekleme 5.999.001 birimdir.

Aşağıdaki satırlarda aynı 4.000 birimlik iş parçalara bölünüyor. Parça maliyeti 4.000’den 400’e indiğinde en uzun bekleme 3.999’dan 786’ya, toplam bekleme 5.999.001’den 1.045.801’e düşüyor. 200 parçada en uzun bekleme 38 birim, toplam bekleme 58.681 birimdir: yüzde birinden azı. Yapılan iş bir birim bile azalmadı; yalnız bölünebilir hâle geldi.

Son sütun bedeli gösteriyor. Tek komutta tarama 6.001. birimde bitiyor, parçalıda 8.001. birimde. Parçalı tarama kendi bitiş süresinden vazgeçerek başkalarının beklemesini satın alır. Tek iş parçacıklı bir depoda bu takas her uzun iş için yeniden yapılır: taramanın ne zaman biteceği genellikle kimseyi ilgilendirmez, arada bekleyen üç bin istek ilgilendirir.

İmleçli Taramanın Kaçırdığı

Parçalama bedava değildir ve bedeli yalnız gecikmiş bitiş değildir. Tek komut bütün taramayı iş parçacığını elinde tutarak yapar, dolayısıyla anahtar alanının değişmeyen bir hâlini görür. İmleçle yürüyen tarama parçalar arasında iş parçacığını bırakır ve o aralıklarda anahtar alanı değişir. Aşağıdaki koşum bunu 200.000 girişlik bir anahtar alanında ölçer; her parçadan sonra iki kitap ödünç verilir ve önbellek girdisi silinir.

// imlecli-tarama.mjs — ayni tarama: tek blokta mi, imlecle parca parca mi
function anahtarAlani(adet) {                      // ekleme sirasini koruyan anahtar listesi
  const depo = new Map();
  const anahtarlar = [];
  for (let i = 1; i <= adet; i += 1) {
    const a = `kitap:${i}`;
    depo.set(a, { rafta: i % 5, sube: (i % 3) + 1 });
    anahtarlar.push(a);
  }
  return { depo, anahtarlar };
}

const ADET = 200000, PARCA = 1000, SILME = 2;      // parca basina 2 silme (odunc verilen kitaplar)

function tekBlok() {                               // komut is parcacigini bastan sona tutar
  const { depo, anahtarlar } = anahtarAlani(ADET);
  let gorulen = 0, eslesen = 0;
  for (const a of anahtarlar) { gorulen += 1; if (depo.get(a).rafta === 0) eslesen += 1; }
  return { ad: "tek blok", enUzunBlok: gorulen, gorulen, eslesen, komut: 1, kacirilan: 0 };
}

function imlecli() {                               // her parca ayri bir komut; arada baskalari calisir
  const { depo, anahtarlar } = anahtarAlani(ADET);
  const ziyaret = new Set();
  let p = 0, gorulen = 0, eslesen = 0, komut = 0, silinen = 0;
  while (p < anahtarlar.length) {
    const son = Math.min(p + PARCA, anahtarlar.length);
    for (let i = p; i < son; i += 1) {             // bu parcanin isi
      const a = anahtarlar[i];
      ziyaret.add(a); gorulen += 1;
      if (depo.get(a).rafta === 0) eslesen += 1;
    }
    p = son; komut += 1;
    for (let s = 0; s < SILME && p < anahtarlar.length; s += 1) {   // parcalar arasinda silme olur
      const yer = (silinen * 977) % p;             // imlecten once bir anahtar silinir
      depo.delete(anahtarlar[yer]);
      anahtarlar.splice(yer, 1);                   // liste kayar: imlecteki giris atlanir
      silinen += 1;
    }
  }
  const kacirilan = anahtarlar.filter((a) => !ziyaret.has(a)).length;   // sonuna kadar duran ama gorulmeyen
  return { ad: `imlecli (${PARCA})`, enUzunBlok: PARCA, gorulen, eslesen, komut, kacirilan };
}

console.log(`anahtar alani=${ADET}  parca=${PARCA}  parca basina silme=${SILME}`);
console.log(["yol", "komut", "en uzun blok", "gorulen giris", "eslesen", "kacirilan"]
  .map((b, i) => (i === 0 ? b.padEnd(16) : b.padStart(15))).join(""));
for (const r of [tekBlok(), imlecli()]) {
  console.log(r.ad.padEnd(16) + [r.komut, r.enUzunBlok, r.gorulen, r.eslesen, r.kacirilan]
    .map((n) => String(n).padStart(15)).join(""));
}
anahtar alani=200000  parca=1000  parca basina silme=2
yol                       komut   en uzun blok  gorulen giris        eslesen      kacirilan
tek blok                      1         200000         200000          40000              0
imlecli (1000)              200           1000         199602          39921            398

Tek blok 200.000 girişi kesintisiz gezer ve tam sayıyı verir: 40.000 eşleşme, sıfır kaçırılan giriş. İmleçli tarama en uzun bloğu 1.000 girişe indirir — az önceki ölçümde beklemeyi iki mertebe düşüren şey buydu — ama 398 giriş hiç görülmez. Bu girişler tarama boyunca varlığını sürdürmüştü; imlecin önünden bir anahtar silindiğinde liste kaydığı için atlandılar.

Bu, tek iş parçacıklı deponun tanı tablosundaki asıl ödünleşimdir. Kesintisiz tarama tam yanıt verir ve herkesi bekletir; imleçli tarama kimseyi bekletmez ve yaklaşık yanıt verir. Bir bakım işi için yaklaşık yanıt genellikle yeterlidir, bir sayım raporu için değildir.

Yavaş Komut Günlüğünün Eşiği

Yukarıdaki ölçümler düzeneğin içini görebildiği için yapılabildi. Çalışan bir depoda hangi komutun uzun sürdüğü doğrudan görünmez; bunun için yavaş komut günlüğü tutulur: süresi bir eşiğin üstünde kalan komutlar son N kaydı tutan bir halka tampona yazılır. Günlüğün kendisi de bellekten yenir, dolayısıyla eşik iki şeyi birden belirler: ne görüleceğini ve kaç bayt tutulacağını. Aşağıdaki koşum 128 kayıtlık bir halkayla beş eşiği tarar (BK16).

// yavas-komut-gunlugu.mjs — esik taramasi: hangi esik sucluyu gosterir, gunluk kac bayt tutar
const HALKA = 128, EK_GIRIS = 16;                  // gunluk halka tampondur: son 128 kayit

function komutlar(adet) {                          // belirlenimli karisim, tohum gorunur
  let tohum = 20250731;
  const rast = () => ((tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648);
  return Array.from({ length: adet }, (_, i) => {
    if (i === 1500) return { ad: "tarama", maliyet: 4000 };         // sucluyu ariyoruz
    const r = rast();
    if (r < 0.95) return { ad: "oku", maliyet: 1 };
    if (r < 0.99) return { ad: "kume-uye", maliyet: 5 + Math.floor(rast() * 36) };
    return { ad: "sirali-kesit", maliyet: 200 };
  });
}

const akis = komutlar(3000);
const kayitBayt = (k, sira) =>                     // gunluk kaydi: komut adi, maliyet, sira
  Buffer.byteLength(JSON.stringify({ ad: k.ad, maliyet: k.maliyet, sira })) + EK_GIRIS;

console.log(`komut=${akis.length}  halka=${HALKA} kayit  toplam is=` +
  `${akis.reduce((t, k) => t + k.maliyet, 0)} birim`);
console.log(["esik", "kaydedilen", "halkada kalan", "tutulan bayt", "tarama halkada mi"]
  .map((b, i) => (i === 0 ? b.padEnd(8) : b.padStart(18))).join(""));
for (const esik of [1, 5, 20, 100, 1000]) {
  const halka = [];
  let kaydedilen = 0;
  akis.forEach((k, i) => {
    if (k.maliyet < esik) return;                  // esigin altindaki komut gunluge girmez
    kaydedilen += 1;
    halka.push({ ...k, sira: i, bayt: kayitBayt(k, i) });
    if (halka.length > HALKA) halka.shift();       // en eski kayit dusurulur
  });
  const bayt = halka.reduce((t, k) => t + k.bayt, 0);
  const var_ = halka.some((k) => k.maliyet === 4000) ? "evet" : "hayir";
  console.log(String(esik).padEnd(8) +
    [kaydedilen, halka.length, bayt, var_].map((n) => String(n).padStart(18)).join(""));
}
komut=3000  halka=128 kayit  toplam is=17267 birim
esik            kaydedilen     halkada kalan      tutulan bayt tarama halkada mi
1                     3000               128              6719             hayir
5                      159               128              7546              evet
20                     108               108              6422              evet
100                     40                40              2502              evet
1000                     1                 1                58              evet

Son sütun eşik seçiminin neden bir ölçüm kararı olduğunu gösteriyor. Eşik 1 iken günlük her komutu kaydediyor: 3.000 kayıt üretiliyor, ama halka yalnız son 128’ini tutabildiği için 4.000 birimlik tarama günlükten düşmüş. Bütün komutları kaydeden bir günlük, aranan komutu kaydetmeyen bir günlüktür.

Eşik 5’te kayıt sayısı 159’a iniyor ve tarama halkada kalıyor. Eşik 20’de kayıt sayısı halkanın altına düşüyor (108) ve artık hiçbir kayıt kaybolmuyor; günlük 6.422 bayt tutuyor. Eşik 1.000’de tek kayıt kalıyor: suçlu görünür ama 200 birimlik sıralı kesit komutlarının varlığı görünmez — oysa toplam 17.267 birimlik işin dörtte biri onlardan geliyor.

Kullanılabilir eşik, kayıt sayısını halka boyunun altında tutan ve normal komutları dışarıda bırakan aralıktır. Bu aralık iş yükünden okunur, önceden bilinemez: eşik önce geniş tutulup kayıt sayısı halka boyunu aşıyorsa daraltılır. Günlüğün bütçesi de aynı tablodadır — 128 kayıt en fazla 7.546 bayt tuttu — ve bu bütçe, sorunun görünür olmasını satın alır.

Özet

  • Tek iş parçacıklı depoda uzun komut yalnız kendi istemcisini geciktirmez: 4.000 birimlik tek komut arkasında 2.001 istek bekletti, en uzun bekleme 3.999, toplam bekleme 5.999.001 birim oldu.
  • Aynı iş 200 parçaya bölündüğünde en uzun bekleme 38, toplam bekleme 58.681 birime indi; yapılan iş değişmedi, yalnız bölünebilir hâle geldi.
  • Parçalamanın bedeli iki kalemdir: tarama 6.001 yerine 8.001. birimde bitti ve imleç önünden silinen anahtarlar yüzünden 398 giriş hiç görülmedi (tek blokta 0).
  • Yavaş komut günlüğünde eşik hem ne görüleceğini hem kaç bayt tutulacağını belirler: eşik 1’de 3.000 kayıt üretildi ve aranan komut 128 kayıtlık halkadan düştü.
  • Kullanılabilir eşik kayıt sayısını halka boyunun altında tutandır; eşik 20’de 108 kayıt, 6.422 bayt ve suçlu komut günlükte kaldı.

Sonraki Adım

Bu kursun bütün ölçümleri deponun yalnız kendi istemcileriyle konuştuğunu varsaydı: yayımlayan taraf tanınan bir uygulama, tarayan komut bir bakım işi, ölçülen gecikme kendi iş yükünün gecikmesiydi. Oysa şimdiye kadar sayılan her maliyet, komutu kimin gönderdiği sorulmadan ödendi. Bütün anahtar alanını silen bir komutun, bütün belleği dolduran bir yazma dizisinin ya da yavaş abone olup tamponu şişiren bir bağlantının önünde hiçbir engel kurulmadı. Sonraki ders bu boşluğu ele alır: varsayılan olarak açık bir kurulumun ne kadar sürede bulunduğunu, komut kısıtlamasının neyi kapattığını ve ağ yalıtımının hangi maliyetle geldiğini ölçer.

İ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