İçeriğe geç
academia.sh

Ders 06 / 22

Bit Eşlemler ve Olasılıksal Yapılar

Sayının kendisi yeterken üyelerin listesini tutmanın bedeli: aynı tekil okur sorusunun tam küme, bit eşlem, olasılıksal üyelik yapısı ve kardinalite kestirimiyle çözülmesi, dört yapının tuttuğu baytın kendi tamponundan okunması, yanlış olumlu oranının ölçülmesi ve kesinlikten vazgeçmenin kaç bayt satın aldığının sayılması.

İçindekiler

Buraya kadarki yapılar saydıkları şeyi tam tutuyor. Bir kümenin kaç üyesi olduğu bilinsin diye üye numaralarının hepsi bellekte duruyor; bir karma yapının kaç alanı olduğu bilinsin diye alan adlarının hepsi bellekte duruyor. Üye geri istendiğinde bu zorunludur. Ama her soru üyeyi geri istemez: kütüphanenin gün sonu raporundaki “bugün kaç ayrı okur ödünç aldı” satırının yanıtı tek bir sayıdır ve o sayıyı üretmek için yüz binlerce okur numarasının bellekte tutulması bir karar olarak hiç sorgulanmadı.

Bu ders o kararı sorgular. İki soru ayrılır: kardinalite kestirimi (“kaç ayrı okur”) ve üyelik (“şu okur bugün ödünç aldı mı”). İkisi farklı yapılarla, farklı bayt bütçeleriyle ve farklı kesinlik derecesiyle yanıtlanabilir.

İki Soru, Dört Yapı

Kütüphanenin ödünç kaydı gün boyunca bellek içi depoda birikir. Gün sonunda rapor tekil okur sayısını ister; gün içinde ödünç ekranı “bu okur bugün zaten işlem yaptı mı” sorusunu sorar.

Kod Varsayım Değer Gerekçe
BY10 kayıtlı okur 2.000.000 üye numarası 1’den başlayan yoğun bir aralık
BY11 günlük ödünç olayı 900.000 bütün şubelerin toplamı
BY12 olayların dağılımı %70’i okurların %15’inden sık ödünç alan bir çekirdek var
BY13 rapor için kabul edilen sapma %2 gün sonu raporu kesin sayı istemiyor

BY10 bu dersin karar verdiren varsayımıdır: üye numarası yoğun bir aralık olduğu için okur numarası doğrudan bir bit sırası olarak kullanılabilir. Aralık seyrek olsaydı aşağıdaki tablonun ikinci satırı bambaşka bir sayı gösterirdi; ders bunu sonunda ölçer.

Kesin İki Yapı

İlk düzenek iki kesin yapıyı aynı veriyle doldurur. Tam küme açık adreslemeli bir karma tablodur ve tuttuğu bayt tamponunun boyudur; bit eşlem okur numarasını doğrudan bit sırası olarak kullanır.

// tekil-sayim.mjs — gunun tekil odunc alan okuru: tam kume ile bit eslem.
// Bayt sayilari yapinin kendi tamponundan okunur, tahmin yok; tohum gorunur.
const OKUR = 2_000_000, OLAY = 900_000, TOHUM = 20260731;
let d = TOHUM;                                        // 32 bit tam sayi uretec, tasma yok
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 okur = () => rast() < 0.7                       // %70 olay, okurlarin %15'inden
  ? 1 + Math.floor(rast() * OKUR * 0.15) : 1 + Math.floor(rast() * OKUR);

class TamKume {                                       // acik adresleme, bos yuva 0
  constructor() { this.k = 1024; this.t = new Int32Array(this.k); this.n = 0; }
  yer(t, k, x) { let i = (Math.imul(x, 2654435761) >>> 0) & (k - 1);
    while (t[i] !== 0 && t[i] !== x + 1) i = (i + 1) & (k - 1); return i; }
  ekle(x) { if ((this.n + 1) * 2 > this.k) this.buyut();
    const i = this.yer(this.t, this.k, x);
    if (this.t[i] === 0) { this.t[i] = x + 1; this.n += 1; } }
  icerir(x) { return this.t[this.yer(this.t, this.k, x)] !== 0; }
  buyut() { const y = new Int32Array(this.k * 2);
    for (const v of this.t) if (v !== 0) y[this.yer(y, this.k * 2, v - 1)] = v;
    this.t = y; this.k *= 2; }
  bayt() { return this.t.byteLength; }
}
class BitEslem {                                      // okur numarasi dogrudan bit sirasidir
  constructor(n) { this.b = new Uint8Array(Math.ceil(n / 8)); }
  ekle(x) { this.b[x >> 3] |= 1 << (x & 7); }
  icerir(x) { return (this.b[x >> 3] & (1 << (x & 7))) !== 0; }
  say() { let s = 0; for (let v of this.b) while (v) { s += v & 1; v >>= 1; } return s; }
  bayt() { return this.b.byteLength; }
}

const kume = new TamKume(), eslem = new BitEslem(OKUR + 1);
for (let i = 0; i < OLAY; i += 1) { const o = okur(); kume.ekle(o); eslem.ekle(o); }
const n = kume.n, tr = (x) => x.toLocaleString("tr-TR");
console.log(`model: ${tr(OKUR)} kayitli okur, ${tr(OLAY)} odunc olayi, tohum ${TOHUM}`);
console.log(`tekil okur: ${tr(n)} (bit eslem ayni sayiyi veriyor: ${tr(eslem.say())})\n`);
console.log("yapi".padEnd(14) + "tutulan bayt".padStart(14) + "tekil basina".padStart(14) +
  "uyelik".padStart(9) + "sayi".padStart(9));
for (const [ad, y] of [["tam kume", kume], ["bit eslem", eslem]])
  console.log(ad.padEnd(14) + tr(y.bayt()).padStart(14) + (y.bayt() / n).toFixed(2).padStart(14) +
    "kesin".padStart(9) + "kesin".padStart(9));

let yanlis = 0;                                       // iki yapi da ayni yaniti veriyor mu
for (let x = 1; x <= OKUR; x += 7) if (kume.icerir(x) !== eslem.icerir(x)) yanlis += 1;
console.log(`\n${tr(Math.ceil(OKUR / 7))} uyelik sorgusunda iki yapinin ayrildigi sorgu: ${yanlis}`);
const SEYREK = 1_000_000_000;                         // 9 haneli uye numarasi kullanilsaydi
console.log(`bit eslem numara araligina baglidir: 1..${tr(OKUR)} icin ${tr(eslem.bayt())} bayt, ` +
  `1..${tr(SEYREK)} icin ${tr(Math.ceil(SEYREK / 8))} bayt (tam kume degismez: ${tr(kume.bayt())})`);
model: 2.000.000 kayitli okur, 900.000 odunc olayi, tohum 20260731
tekil okur: 482.085 (bit eslem ayni sayiyi veriyor: 482.085)

yapi            tutulan bayt  tekil basina   uyelik     sayi
tam kume           4.194.304          8.70    kesin    kesin
bit eslem            250.001          0.52    kesin    kesin

285.715 uyelik sorgusunda iki yapinin ayrildigi sorgu: 0
bit eslem numara araligina baglidir: 1..2.000.000 icin 250.001 bayt, 1..1.000.000.000 icin 125.000.000 bayt (tam kume degismez: 4.194.304)

Bu sayılar ölçüm sınıfındadır; tekil okur sayısı tohuma bağlı, yapıların tuttuğu bayt bağlı değil — o sayı doğrudan tamponun boyudur ve aynı giriş sayısında her koşumda aynıdır.

Bit eşlem tam kümeden on altı kat az yer tutuyor (250.001’e karşı 4.194.304 bayt) ve hiçbir şey kaybetmiyor: tekil sayısı aynı, 285.715 üyelik sorgusunun hiçbirinde iki yapı ayrılmıyor. Bunun nedeni bit eşlemin okur numarasını veri olarak değil adres olarak kullanmasıdır. Tam küme numarayı saklamak zorundadır, çünkü bir yuvada hangi numaranın durduğu ancak yazılıysa bilinir; bit eşlemde numaranın nerede duracağı zaten numaranın kendisidir, dolayısıyla saklanacak bir şey kalmaz ve giriş başına maliyet 8,70 bayttan 0,52 bayta iner.

Kaybedilen şey son satırda: bit eşlemin maliyeti kaç okur ödünç aldığına değil, numara aralığının genişliğine bağlıdır. Kütüphane dokuz haneli üye numarasına geçse aynı 482.085 okur için 250.001 bayt yerine 125.000.000 bayt gerekirdi; tam kümenin maliyeti değişmezdi. Bit eşlem, aralığın yoğun olduğu yerde bedava, seyrek olduğu yerde ödenemez bir yapıdır.

Kesinlikten Vazgeçmek

İki kesin yapı da her okuru ayrı ayrı temsil ediyor. Bir sonraki adım bu temsili bırakmaktır: aynı soruyu, hangi okurun içeride olduğunu belirsiz bırakan yapılarla yanıtlamak.

// olasiliksal-sayim.mjs — ayni gunun ayni iki sorusu, bu kez kesinlikten vazgecerek.
// Uyelik yapisi ve kardinalite kestirimi; dogruluk olcusu icin ayrica tam liste tutulur.
const OKUR = 2_000_000, OLAY = 900_000, TOHUM = 20260731, SORGU = 300_000;
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 okur = () => rast() < 0.7
  ? 1 + Math.floor(rast() * OKUR * 0.15) : 1 + Math.floor(rast() * OKUR);
const karma = (x, tohum) => { let h = Math.imul(x ^ tohum, 0x85ebca6b) >>> 0;
  h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35) >>> 0; return (h ^ (h >>> 16)) >>> 0; };

const gercek = new Uint8Array(OKUR + 1);              // yalniz dogruluk olcusu, olculen yapi degil
for (let i = 0; i < OLAY; i += 1) gercek[okur()] = 1;
const uyeler = [], disaridakiler = [];
for (let x = 1; x <= OKUR; x += 1) (gercek[x] ? uyeler : disaridakiler).push(x);
const sorgular = disaridakiler.slice(0, SORGU), n = uyeler.length;
const tr = (x) => x.toLocaleString("tr-TR");
console.log(`model: ${tr(n)} tekil okur, ${tr(SORGU)} olumsuz uyelik sorgusu, tohum ${TOHUM}\n`);

console.log("olasiliksal uyelik yapisi (k = 3 karma)");
console.log("bit".padEnd(10) + "tutulan bayt".padStart(14) + "tekil basina".padStart(14) +
  "yanlis olumlu".padStart(15) + "beklenen".padStart(10) + "kacirilan".padStart(11));
for (const p of [17, 18, 19, 20, 21, 22]) {
  const m = 1 << p, K = 3, b = new Uint8Array(m / 8);
  const yerler = (x) => Array.from({ length: K }, (_, i) => karma(x, 0x1000 + i * 0x9e3779b1) % m);
  for (const x of uyeler) for (const i of yerler(x)) b[i >> 3] |= 1 << (i & 7);
  const icerir = (x) => yerler(x).every((i) => (b[i >> 3] & (1 << (i & 7))) !== 0);
  let yo = 0, kacan = 0;
  for (const x of sorgular) if (icerir(x)) yo += 1;
  for (const x of uyeler) if (!icerir(x)) kacan += 1;
  const beklenen = (1 - Math.exp((-K * n) / m)) ** K;
  console.log(`2^${p}`.padEnd(10) + tr(b.byteLength).padStart(14) +
    (b.byteLength / n).toFixed(2).padStart(14) +
    `%${((yo / SORGU) * 100).toFixed(2)}`.padStart(15) +
    `%${(beklenen * 100).toFixed(2)}`.padStart(10) + `${kacan}`.padStart(11));
}

console.log("\nkardinalite kestirimi (yalniz sayi; uyelik sorusuna yanit vermez)");
console.log("yazmac".padEnd(10) + "tutulan bayt".padStart(14) + "tekil basina".padStart(14) +
  "kestirim".padStart(12) + "sapma".padStart(9) + "kuramsal".padStart(10));
for (const p of [8, 10, 12, 14]) {
  const m = 1 << p, R = new Uint8Array(m);
  for (const x of uyeler) { const h = karma(x, 0x9e37), i = h >>> (32 - p), w = (h << p) >>> 0;
    const r = w === 0 ? 33 - p : Math.clz32(w) + 1; if (r > R[i]) R[i] = r; }
  let z = 0, sifir = 0;
  for (const v of R) { z += 2 ** -v; if (v === 0) sifir += 1; }
  let e = (0.7213 / (1 + 1.079 / m)) * m * m / z;
  if (e <= 2.5 * m && sifir > 0) e = m * Math.log(m / sifir);   // dogrusal sayim duzeltmesi
  console.log(`2^${p}`.padEnd(10) + tr(R.byteLength).padStart(14) +
    (R.byteLength / n).toFixed(4).padStart(14) + tr(Math.round(e)).padStart(12) +
    `%${(((e - n) / n) * 100).toFixed(2)}`.padStart(9) +
    `%${((1.04 / Math.sqrt(m)) * 100).toFixed(2)}`.padStart(10));
}
model: 482.085 tekil okur, 300.000 olumsuz uyelik sorgusu, tohum 20260731

olasiliksal uyelik yapisi (k = 3 karma)
bit         tutulan bayt  tekil basina  yanlis olumlu  beklenen  kacirilan
2^17              16.384          0.03         %99.99   %100.00          0
2^18              32.768          0.07         %98.83    %98.80          0
2^19              65.536          0.14         %82.26    %82.16          0
2^20             131.072          0.27         %42.09    %41.89          0
2^21             262.144          0.54         %12.41    %12.37          0
2^22             524.288          1.09          %2.53     %2.48          0

kardinalite kestirimi (yalniz sayi; uyelik sorusuna yanit vermez)
yazmac      tutulan bayt  tekil basina    kestirim    sapma  kuramsal
2^8                  256        0.0005     476.215   %-1.22     %6.50
2^10               1.024        0.0021     481.111   %-0.20     %3.25
2^12               4.096        0.0085     474.849   %-1.50     %1.63
2^14              16.384        0.0340     478.877   %-0.67     %0.81

Üst tabloda kaçırılan sütunu her satırda sıfır: olasılıksal üyelik yapısı bir üyeyi asla “yok” diye yanıtlamaz, çünkü eklenen her üyenin bitleri kurulmuştur ve hiç silinmez. Hata tek yönlüdür ve adı yanlış olumlu: yapı, hiç eklenmemiş bir okur için de üç bitin üçünü birden kurulmuş bulabilir. Ölçülen oran kuramsal oranla satır satır örtüşüyor (2^21’de %12,41’e karşı %12,37), yani bu oran bir sürpriz değil, yapının boyuyla seçilen bir niceliktir.

Aynı sözcük çifti yazılım testi bağlamında da geçer; oradaki yanlış alarm kusursuz kodu kusurlu diye işaretleyen bir testin giderilebilir hatasıdır, burada ölçülen yanlış olumlu oranı ise yapının tanımına yazılıdır: bayt eklenerek düşürülebilir, sıfırlanamaz.

Alt tablo takasın en keskin biçimidir. 4.096 bayt tutan yazmaç dizisi 482.085 tekil okuru %1,50 sapmayla sayıyor: tam kümenin bin yirmi dört katı, bit eşlemin altmış bir katı az yer tutuyor. Seçilecek boy son sütundan okunur, ölçülen sapmadan değil. 256 baytlık dizi bu koşumda %1,22 sapmayla çıktı ama kuramsal hata payı %6,50; BY13’ün %2’lik toleransını güvenle karşılayan ilk boy kuramsal payı %1,63 olan 4.096 bayttır. Karşılığında verdiği şey mutlaktır: bu yapı “şu okur ödünç aldı mı” sorusuna hiçbir kesinlik derecesinde yanıt veremez, çünkü okurun kendisi hiçbir zaman temsil edilmemiştir; yalnız karmasının baştaki sıfırlarının kaç tane olduğu tutulmuştur.

Hangi Yapı Neyi Satın Alır

Dört yapı aynı gün için sırasıyla 4.194.304, 250.001, 524.288 ve 4.096 bayt tutuyor. Sıralama şaşırtıcı bir yerde kırılıyor: yoğun bir numara aralığında olasılıksal üyelik yapısı bit eşleme yenilir. %2,53 yanlış olumluyla çalışan yapı 524.288 bayt istiyor, kesin bit eşlem 250.001 bayt. Yani bu koşullarda kesinlikten vazgeçmek bellek kazandırmıyor, tersine kaybettiriyor.

Olasılıksal üyelik yapısının kazandığı yer aralığın seyrek olduğu yerdir. Kütüphanenin ikinci sorusu şudur: bugün hangi kitap kimliği en az bir kez elden geçti? Kitap kimliği yoğun bir sayı değil, on üç haneli bir dizgi; bit eşlem için adres yok. Orada tam küme dizginin kendisini tutmak zorundadır, olasılıksal yapı ise dizginin karmasını kullanır ve boyu üye sayısına bağlı kalır. Seçim kuralı yapının adında değil, anahtar uzayının yoğunluğundadır.

Bir de birleştirme var. Haftalık tekil okur sorusu yedi günün sonucunu birleştirmeyi gerektirir. Bit eşlemler bit düzeyinde “veya” ile birleşir ve sonuç kesin kalır; kardinalite kestirimleri yazmaç başına en büyüğü alınarak birleşir ve sapma büyümez; tam kümeler birleştirilirken bütün üyeler yeniden dokunulur ve maliyet gün sayısıyla çarpılır. Günlük 4.096 baytlık yazmaç dizisi bir yıl saklandığında 1.495.040 bayt tutar — tek bir günün tam kümesinden hâlâ küçüktür ve yıl içindeki her alt aralık için ayrı yanıt verebilir.

Özet

  • Tekil sayım ile üyelik iki ayrı sorudur ve ayrı yapılarla yanıtlanır: kardinalite kestirimi sayıyı verir ama üyeliği hiçbir kesinlikte yanıtlamaz, çünkü üyeyi temsil etmez.
  • Yoğun bir numara aralığında bit eşlem tam kümeden on altı kat az yer tutar (250.001’e karşı 4.194.304 bayt) ve hiçbir şey kaybetmez; maliyeti giriş sayısına değil aralık genişliğine bağlıdır ve dokuz haneli numarada 125.000.000 bayta çıkar.
  • Olasılıksal üyelik yapısının hatası tek yönlüdür: kaçırılan üye sıfır, yanlış olumlu oranı ise yapının boyuyla seçilir — 131.072 baytta %42,09, 524.288 baytta %2,53 ve ölçülen oran kuramsal oranla örtüşür.
  • Yoğun aralıkta kesinlikten vazgeçmek bellek kazandırmaz: %2,53 hatalı yapı 524.288 bayt, kesin bit eşlem 250.001 bayt ister. Olasılıksal yapı ancak anahtar uzayı seyrekken kazanır.
  • Kardinalite kestirimi 4.096 baytla %1,50 sapma verir; tam kümenin bin yirmi dört katı az yer tutar ve karşılığında hangi okurun sayıldığı bilgisini geri alınamaz biçimde bırakır.

Sonraki Adım

Bu derste ölçülen yapılar zamanı hiç umursamadı. Bit eşlem “bugün ödünç aldı mı” sorusuna yanıt veriyor ama ne zaman aldığını bilmiyor; kardinalite kestirimi gün sonunda bir sayı üretiyor ama o sayının hangi sırayla toplandığını söyleyemiyor. Aynı gün içinde okunması gereken bir soru daha var: kütüphanenin raf yerleştirme ekibi, gecikme bildirimi işi ve arama dizini güncellemesi, olayları oldukları sırayla ve her biri kendi kaldığı yerden görmek zorunda. Buraya kadarki hiçbir yapı bunu vermiyor — küme sıra tutmuyor, sayaç geçmiş tutmuyor, liste ise okuyan her tarafın kendi konumunu ayrı ayrı hatırlamasına yardım etmiyor. Sonraki ders olayları sırasıyla saklayan ve her okuyan tarafın konumunu yapının kendisinde tutan yapıyı ele alı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