İçeriğe geç
academia.sh

Ders 07 / 22

Akışlar

Sıralı olay günlüğünün bir veri yapısı olarak ele alınması: giriş kimliğinin zaman ve sıradan kurulması, giriş başına baytın alan alan sayılması, uzunluk ve süre sınırının bir günlük etkinlik akışında karşılaştırılması ve okunmamış payın tutulan baytı belirlemesi.

İçindekiler

Önceki yapıların hiçbiri sırayı ve okuma konumunu birlikte tutmuyordu. Küme üyeyi tutar ama girişlerin hangi sırayla eklendiğini unutur; sayaç toplamı tutar ama geçmişi bırakmaz; liste sırayı tutar ama okuyan her tarafın nerede kaldığını okuyanın kendisine bırakır. Kütüphanenin üç ayrı işi — raf yerleştirme, gecikme bildirimi ve arama dizini güncellemesi — aynı olay dizisini oldukları sırayla ve her biri kendi kaldığı yerden görmek zorunda.

Akış bunu tek bir yapıda birleştirir: girişler eklendikleri sırayla durur, her girişin sıralanabilir bir kimliği vardır ve her tüketici grubu yalnız bir sınır kimliği tutarak nerede kaldığını hatırlar. Bu ders akışı bir ileti kanalı olarak değil, bellekte yer tutan bir veri yapısı olarak ele alır: giriş kaç bayt, kimlik ne işe yarıyor ve girişler ne zaman atılıyor.

Giriş Kimliği

Kimlik iki parçadan kurulur: girişin eklendiği andaki saat ve o saat içindeki sıra. Saat ilerlemediğinde sıra ilerler, dolayısıyla ayrı bir sayaç yapısına gerek kalmadan iki giriş asla aynı kimliği almaz.

// giris-kimligi.mjs — akis girisinin kimligi ve bayti. Kimlik <zaman>-<sira> biciminde
// uretilir: saat ilerlemediginde sira ilerler, boylece ayni milisaniyede cakisma olmaz.
const uretec = () => { let sonMs = -1, sira = 0;
  return (ms) => { if (ms === sonMs) sira += 1; else { sonMs = ms; sira = 0; } return `${ms}-${sira}`; }; };
const uret = uretec();
const kimlikler = [999, 1000, 1000, 1000, 1001, 1001].map(uret);
console.log("bir yigilmada uretilen kimlikler: " + kimlikler.join("  "));

const cozumle = (k) => k.split("-").map(Number);
const kucukMu = (a, b) => { const [x, y] = cozumle(a), [p, q] = cozumle(b);
  return x !== p ? x < p : y < q; };
console.log(`sayisal karsilastirma  999-0 < 1000-0: ${kucukMu("999-0", "1000-0")}`);
console.log(`dizgi karsilastirmasi  999-0 < 1000-0: ${"999-0" < "1000-0"} (kimlik dizgi olarak siralanmaz)`);
const sinir = "1000-1";                       // tuketici grubunun en son onayladigi kimlik
console.log(`${sinir} sonrasi okunacak giris: ` + kimlikler.filter((k) => kucukMu(sinir, k)).join(" "));

const USTVERI = 24, GOSTERGE = 8;             // kimlik 16 + zincir gostergesi 8; alan basina 8
const g = { tur: "odunc", okur: "418302", kitap: "9780000041173", sube: "sube-3" };
let toplam = USTVERI;
console.log("\nalan".padEnd(8) + "ad".padStart(4) + "deger".padStart(7) + "gosterge".padStart(10) +
  "bayt".padStart(6));
for (const [a, v] of Object.entries(g)) {
  const b = Buffer.byteLength(a) + Buffer.byteLength(v) + GOSTERGE; toplam += b;
  console.log(a.padEnd(8) + String(Buffer.byteLength(a)).padStart(4) +
    String(Buffer.byteLength(v)).padStart(7) + String(GOSTERGE).padStart(10) + String(b).padStart(6));
}
console.log(`ustveri`.padEnd(8) + "".padStart(4) + "".padStart(7) + "".padStart(10) +
  String(USTVERI).padStart(6));
console.log(`giris toplami: ${toplam} bayt; ayni veri alan adlari olmadan ` +
  `${toplam - Object.keys(g).reduce((s, a) => s + Buffer.byteLength(a), 0)} bayt tutardi`);
bir yigilmada uretilen kimlikler: 999-0  1000-0  1000-1  1000-2  1001-0  1001-1
sayisal karsilastirma  999-0 < 1000-0: true
dizgi karsilastirmasi  999-0 < 1000-0: false (kimlik dizgi olarak siralanmaz)
1000-1 sonrasi okunacak giris: 1000-2 1001-0 1001-1

alan     ad  deger  gosterge  bayt
tur        3      5         8    16
okur       4      6         8    18
kitap      5     13         8    26
sube       4      6         8    18
ustveri                          24
giris toplami: 102 bayt; ayni veri alan adlari olmadan 86 bayt tutardi

Kimliğin üç işi var. Birincisi sıra: girişler kimliğe göre sıralanır ve bu sıra ekleme sırasıdır. İkincisi sınır: bir tüketici grubunun tuttuğu tek şey en son onayladığı kimliktir; “1000-1 sonrası” ifadesi grubun okuma konumunu on altı bayta indirir. Üçüncüsü çakışmazlık: aynı milisaniyeye düşen üç giriş 1000-0, 1000-1 ve 1000-2 olur.

Karşılaştırmanın sayısal olduğu satır ihmal edilecek bir ayrıntı değil. Kimlik bir dizgi gibi karşılaştırılırsa 999-0 ile 1000-0 ters sıralanır, çünkü basamak sayıları farklıdır; sıra güvencesi kimliğin biçiminden değil, çözümlenerek karşılaştırılmasından gelir.

Bayt tablosu bir tasarım kararını görünür kılıyor. Girişin 102 baytının 16’sı alan adlarıdır ve bu adlar her girişte yeniden yazılır. Günde 600.000 giriş için bu, yalnız dört sözcüğün tekrarı olarak 9,15 MiB demektir. Kısa alan adları ya da konuma dayalı bir düzen bu payı düşürür; karşılığında akışı okuyan tarafın alan sırasını bilmesi gerekir, yani günlüğün kendi kendini açıklaması kaybedilir.

Bir Günün Akışı

Akışın bellek maliyeti giriş baytı ile kaç girişin tutulduğunun çarpımıdır ve ikinci çarpanı belirleyen şey kırpma politikasıdır.

Kod Varsayım Değer Gerekçe
BY14 günlük etkinlik girişi 600.000 bütün şubelerin ödünç, iade, ayırtma ve raf hareketi
BY15 giriş üstverisi 24 bayt kimlik 16 bayt, zincir göstergesi 8 bayt
BY16 tüketici grubu 3 grup, 1500 / 1000 / 700 giriş/dk üç işin işleme hızı ayrı
BY17 saatlik yoğunluk eğrisi tepe çarpanı 2,8 kütüphane gündüz açık, gece neredeyse durgun

BY16 bu dersin karar verdiren varsayımıdır: üç grubun hızı eşit olsaydı kırpma politikası tek başına belleği belirlerdi, eşit olmadığı için belleği en yavaş grup belirliyor.

// akis.mjs — bir gunluk etkinlik akisi: kirpma politikalari ve okunmamis payin bellege etkisi.
// Gun 1440 dakikalik adima bolunur, varislar saat carpanlarindan uretilir, tohum gorunur.
const OLAY = 600_000, TOHUM = 20260731, DK = 1440, USTVERI = 24, GOSTERGE = 8;
const CARPAN = [0.05, 0.03, 0.02, 0.02, 0.03, 0.08, 0.2, 0.6, 1.6, 2.2, 2.6, 2.4,
                1.8, 2.0, 2.6, 2.8, 2.5, 2.0, 1.4, 0.9, 0.5, 0.3, 0.15, 0.08];
const GRUP = [["raf yerlestirme", 1500], ["gecikme bildirimi", 1000], ["arama dizini", 700]];
let d = TOHUM;
const rast = () => { d = (d + 0x6D2B79F5) | 0; let t = Math.imul(d ^ (d >>> 15), 1 | d);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 2 ** 32; };
const TUR = ["odunc", "iade", "ayirtma", "raf-degisimi"];
const giris = (i) => ({ tur: TUR[Math.floor(rast() * 4)], okur: String(100000 + (i % 900000)),
  kitap: String(9780000000000 + (i % 120000)), sube: `sube-${1 + Math.floor(rast() * 6)}` });
const bayt = (g) => USTVERI + Object.entries(g)
  .reduce((s, [a, v]) => s + Buffer.byteLength(a) + Buffer.byteLength(v) + GOSTERGE, 0);

const olcek = CARPAN.reduce((s, c) => s + c, 0) * 60;
const gb = new Uint16Array(OLAY * 2), gt = new Uint16Array(OLAY * 2);
let n = 0;
for (let t = 0; t < DK; t += 1) {
  const k = Math.round((OLAY * CARPAN[Math.floor(t / 60)]) / olcek);
  for (let i = 0; i < k; i += 1) { gb[n] = bayt(giris(n)); gt[n] = t; n += 1; }
}
const on = new Float64Array(n + 1);                          // kumulatif bayt
for (let i = 0; i < n; i += 1) on[i + 1] = on[i] + gb[i];
const tr = (x) => x.toLocaleString("tr-TR", { maximumFractionDigits: 2 });
const saatDk = (t) => `${Math.floor(t / 60)}:${String(t % 60).padStart(2, "0")}`;
let enAz = gb[0], enCok = gb[0];
for (let j = 1; j < n; j += 1) { if (gb[j] < enAz) enAz = gb[j]; if (gb[j] > enCok) enCok = gb[j]; }
console.log(`model: ${tr(n)} giris/gun, ${DK} dakikalik adim, tohum ${TOHUM}`);
console.log(`giris bayti: ortalama ${tr(on[n] / n)}, en kucuk ${enAz}, en buyuk ${enCok}; ` +
  `gunun tamami ${tr(Math.round(on[n] / 1024 ** 2))} MiB\n`);

function kos(ad, kirp, durakla = null) {
  const imlec = GRUP.map(() => 0);
  let bas = 0, son = 0, i = 0, tepe = 0, tepeBayt = 0, tepeDk = 0, kayip = 0;
  for (let t = 0; t < DK; t += 1) {
    while (i < n && gt[i] === t) { i += 1; son += 1; }
    for (let g = 0; g < GRUP.length; g += 1) {
      const durgun = durakla && g === durakla[0] && t >= durakla[1] && t < durakla[2];
      if (!durgun) imlec[g] = Math.min(son, imlec[g] + GRUP[g][1]);
    }
    bas = Math.max(bas, kirp(son, t, imlec));
    for (let g = 0; g < GRUP.length; g += 1)
      if (imlec[g] < bas) { kayip += bas - imlec[g]; imlec[g] = bas; }
    if (son - bas > tepe) { tepe = son - bas; tepeBayt = on[son] - on[bas]; tepeDk = t; }
  }
  console.log(ad.padEnd(26) + tr(son - bas).padStart(11) + tr(tepe).padStart(11) +
    tr(Math.round(tepeBayt / 1024)).padStart(11) + saatDk(tepeDk).padStart(9) +
    tr(kayip).padStart(11));
}
const ilkAdim = (t, T) => { let l = 0, r = n; while (l < r) {
  const m = (l + r) >> 1; if (gt[m] < t - T) l = m + 1; else r = m; } return l; };
console.log("politika".padEnd(26) + "gun sonu".padStart(11) + "tepe giris".padStart(11) +
  "tepe KiB".padStart(11) + "tepe dk".padStart(9) + "kacirilan".padStart(11));
kos("kirpma yok", () => 0);
kos("uzunluk 50.000", (son) => son - 50_000);
kos("sure 60 dk", (son, t) => ilkAdim(t, 60));
kos("guvenli (en yavas grup)", (son, t, im) => Math.min(...im));
kos("guvenli + 30 dk duraklama", (son, t, im) => Math.min(...im), [2, 1020, 1050]);

console.log("\ngrup".padEnd(26) + "hiz/dk".padStart(9) + "tepe okunmamis".padStart(16) +
  "tepe KiB".padStart(11) + "tepe dk".padStart(9) + "gun sonu".padStart(10));
const imlec = GRUP.map(() => 0), tepe = GRUP.map(() => [0, 0, 0]);
let son = 0, i = 0;
for (let t = 0; t < DK; t += 1) {
  while (i < n && gt[i] === t) { i += 1; son += 1; }
  for (let g = 0; g < GRUP.length; g += 1) {
    imlec[g] = Math.min(son, imlec[g] + GRUP[g][1]);
    if (son - imlec[g] > tepe[g][0]) tepe[g] = [son - imlec[g], on[son] - on[imlec[g]], t];
  }
}
for (let g = 0; g < GRUP.length; g += 1)
  console.log(GRUP[g][0].padEnd(26) + tr(GRUP[g][1]).padStart(9) + tr(tepe[g][0]).padStart(16) +
    tr(Math.round(tepe[g][1] / 1024)).padStart(11) + saatDk(tepe[g][2]).padStart(9) +
    tr(son - imlec[g]).padStart(10));
model: 600.000 giris/gun, 1440 dakikalik adim, tohum 20260731
giris bayti: ortalama 104, en kucuk 101, en buyuk 109; gunun tamami 60 MiB

politika                     gun sonu tepe giris   tepe KiB  tepe dk  kacirilan
kirpma yok                    600.000    600.000     60.935    23:59          0
uzunluk 50.000                 50.000     50.000      5.078     8:45     38.920
sure 60 dk                      1.856     63.488      6.447    15:59     46.220
guvenli (en yavas grup)             0     88.920      9.031    17:59          0
guvenli + 30 dk duraklama           0    109.920     11.163    17:59          0

grup                        hiz/dk  tepe okunmamis   tepe KiB  tepe dk  gun sonu
raf yerlestirme               1.500               0          0     0:00         0
gecikme bildirimi             1.000           2.520        256    15:59         0
arama dizini                    700          88.920      9.031    17:59         0

Bu sayılar ölçüm sınıfındadır; girişlerin dağılımı saat eğrisine ve tohuma bağlı, politikalar arasındaki fark bağlı değil.

Okunmamış Pay

Üst tablo dört politikayı aynı gün üzerinde karşılaştırıyor. Kırpma yok akışı 60.935 KiB’ye çıkarıyor ve hiçbir girişi kaçırmıyor: geçmiş sorgulanabilir kalıyor, bedeli günün tamamının bellekte durması. Uzunluk 50.000 belleği 5.078 KiB’de sabitliyor — on iki kat az — ama arama dizini 38.920 girişi hiç görmeden kaybediyor. Süre 60 dk ilk bakışta daha ölçülü görünüyor, gün sonunda yalnız 1.856 giriş tutuyor; oysa tepede 63.488 girişe çıkıyor ve 46.220 giriş kaçırıyor. Aradaki fark politikanın neyi sabitlediğidir: uzunluk sınırı belleği sabitler, süre sınırı yaşı sabitler ve belleği varış hızına bırakır. Yoğunluk üç katına çıkarsa süre sınırlı akış üç kat yer tutar.

Güvenli kırpma dördüncü seçenektir: bir giriş, bütün gruplar onu okuyana kadar silinmez. Kaçırılan sıfıra iner, tutulan bayt 9.031 KiB’de tepe yapar ve gün sonunda sıfırlanır. Ama tavan artık operatörün elinde değildir. Alt tablo bunu gösteriyor: raf yerleştirme hiç geride kalmıyor, gecikme bildirimi tepede 2.520 giriş biriktiriyor, arama dizini 88.920. Akışın tuttuğu 9.031 KiB’nin tamamı en yavaş grubun okumadığı paydır — akış bir kuyruk olduğu için değil, en gerideki sınır kimliğinden öncesini atamadığı için.

Son satır bunun ne kadar keskin olduğunu ölçüyor. Arama dizini tepe saatte otuz dakika duraksa akış 109.920 girişe çıkıyor: tam 21.000 giriş ve 2.132 KiB fazla, yani duraklama süresi çarpı grubun hızı. Bellek maliyeti burada akışın değil, akışı okumayan tarafın kararıdır.

Bunun karşıtı da aynı ölçüde nettir: okuyan taraf sayısı belleği artırmaz. Üç grup üç sınır kimliği tutar, on altışar bayttan kırk sekiz bayt; akışa dördüncü, beşinci, onuncu grup eklemek 60 MiB’lik gövdeye on altı bayt ekler. Akışı bir listeden ayıran şey budur: liste okundukça tüketilir ve ikinci okuyucu için ikinci bir kopya gerekir, akış bir kez tutulur ve konum okuyanın tarafında durur.

Özet

  • Giriş kimliği zaman ile sıradan kurulur ve üç iş görür: ekleme sırasını verir, aynı milisaniyedeki girişleri ayırır ve bir tüketici grubunun okuma konumunu on altı bayta indirir; karşılaştırma sayısal olmak zorundadır, dizgi karşılaştırması 999-0 ile 1000-0’ı ters sıralar.
  • Giriş baytının bir payı her seferinde tekrarlanan alan adlarıdır: 102 baytın 16’sı, günde 9,15 MiB. Kısaltmak akışın kendi kendini açıklamasını kaybettirir.
  • Uzunluk sınırı belleği sabitler (5.078 KiB), süre sınırı yaşı sabitler ve belleği varış hızına bırakır (tepede 63.488 giriş); ikisi de en yavaş grubu bilmediği için 38.920 ve 46.220 giriş kaçırır.
  • Güvenli kırpmada kaçırma sıfırdır ama tavan operatörün elinde değildir: tutulan 9.031 KiB’nin tamamı en yavaş grubun okunmamış payıdır ve o grup otuz dakika duraklarsa akış 21.000 giriş (2.132 KiB) büyür.
  • Okuyan taraf sayısı belleği artırmaz, okumayan taraf artırır: üç grubun toplam defter maliyeti kırk sekiz bayttır.

Sonraki Adım

Akışın kimliği sıralanabilir olduğu için “şu andan sonrası” sorusu bir arama değil, bir konum belirlemesiydi. Kütüphanenin bir sorusu daha var ve o sorunun sıralanabilir bir anahtarı yok: okur bulunduğu noktaya en yakın toplama noktasını istiyor. İki boyutlu bir konumu tek bir eksende sıralamanın her yolu bazı komşuları birbirinden uzağa düşürür, dolayısıyla ne sıralı küme ne de akış bu soruya doğrudan yanıt verir. Sonraki ders aynı yakınlık sorgusunu bellek içi bir yapıyla kurar ve düz taramanın taradığı aday sayısıyla karşılaştırır.

İ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