---
title: 'Karşılaştırmalı Ölçüm Disiplini'
source: 'https://academia.sh/tr/kurslar/islevsel-olmayan-test/karsilastirmali-olcum-disiplini'
course: 'İşlevsel Olmayan Test'
language: tr
updated: '2026-08-17T18:10:51+00:00'
license: 'CC BY-SA 4.0'
---

# Karşılaştırmalı Ölçüm Disiplini

Bir başarım farkının anlamlı sayılabilmesinin koşulu: ısınma penceresinin istek bloklarıyla ölçülmesi, aynı kodun iki kol olarak koşturulmasıyla düzeneğin kendi gürültüsünün sayılması, tek tura bakan kararın gerilemeyi tersine okuduğu turun gösterilmesi ve eşik ile tur sayısının birlikte taranarak yanlış geçme–yanlış kalma dengesinin kurulması.

Önceki dersin payları tek bir taban koşumuna bölündü ve bir pay 1'i aştı. Nedeni bir kusur
değildi: aynı düzenek iki kez koşturulunca iki ayrı sayı verir. Düzenek ilk saniyeyi de
ölçüme katmıyordu — bir ısınma etkisinin varlığı kabul edilmiş, büyüklüğü hiç ölçülmemişti.
Bu ders ölçümün kendisini sınar.

Soru bir sürüm kararıdır. Ödünç servisinin **aday** sürümü gişe ekranına üyenin gecikmiş
kitap sayısını ekliyor ve bunun için istek başına bir sorgu daha çalıştırıyor. Yayım kapısı
şunu sormak zorunda: aday sürümün ölçümü yüksek çıktığında fark sürümden mi, ölçümden mi?

## Bir Farkın Anlamlı Sayılması

Aynı düzeneğin iki koşumunun aynı sayıyı vermemesine **gürültü** (noise), sayıların koşumdan
koşuma yayılmasına **koşumlar arası değişkenlik** denir. Bir sürüm farkı ancak bu yayılımdan
ayrılabildiği ölçüde okunabilir.

**IO9 (varsayım) — aday sürümün ölçümü tabanın ölçümünü yüzde beşten fazla aşarsa gerileme
sayılır ve yayım durur.** Kaynağı bir gereksinim ya da ölçüm değil, kapı duyarlılığı
için seçilmiş bir sayıdır; aşağıda taranır. Kararın sahibi yayım kapısıdır: kırmızıya
döndüğünde aday sürüm çıkmaz.

Eşiğin iki hata biçimini sayabilmek için düzenek üç kolla koşar. **taban** ile **denetim**
kolları **aynı kodu** çalıştırır, **aday** kolu yeni sürümü. Denetim kolunun gerçek farkı
sıfırdır; kapı onu kırmızıya döndürürse bu bir **yanlış kalmadır**. Aday kolunun farkı
gerçektir; kapı onu geçirirse bu bir **yanlış geçmedir**. İki özdeş kolu
koşturmak, düzeneğin kendi hatasını okumanın en ucuz yoludur.

## Düzenek

Ölçüm on **tur** yapar; her tur üç koşumdur ve her koşum kendi sunucu süreciyle başlar. Üç kolun
art arda koşması, makine yükündeki bir kaymanın tek bir kola yüklenmesini engeller.

```js
// kutuphane/sunucu.mjs — odunc ucu. Aday surum (b) yanita gecikmis kitap sayisini da ekler,
// yani istek basina bir sorgu daha calistirir. Argumanlar: <baglanti-noktasi> <surum a|b>
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const UYE = 500, BASLANGIC = 80000, SON = 40, GEC = 30;   // katalog boyu, liste boyu, gecikme gunu
const port = Number(process.argv[2]), surum = process.argv[3];

if (Number.isInteger(port) === false || ["a", "b"].includes(surum) === false) {
  console.log("kullanim: node kutuphane/sunucu.mjs <baglanti-noktasi> <surum a|b>");
} else {
  const db = new DatabaseSync(":memory:");
  db.exec("CREATE TABLE odunc(no INTEGER PRIMARY KEY, uye_no TEXT, kitap_no TEXT, gun INT)");
  db.exec("CREATE INDEX odunc_uye ON odunc(uye_no)");
  db.exec("BEGIN");
  const doldur = db.prepare("INSERT INTO odunc(uye_no, kitap_no, gun) VALUES(?,?,?)");
  for (let i = 0; i < BASLANGIC; i += 1) doldur.run(`U-${i % UYE}`, `K-${i % 9000}`, i % 400);
  db.exec("COMMIT");

  const say = db.prepare("SELECT COUNT(*) AS n FROM odunc WHERE uye_no = ?");
  const son = db.prepare("SELECT no, kitap_no, gun FROM odunc WHERE uye_no = ? LIMIT ?");
  const gec = db.prepare("SELECT COUNT(*) AS n FROM odunc WHERE uye_no = ? AND gun > ?");
  let istek = 0, sorgu = 0;

  createServer((gelen, yanit) => {
    const u = new URL(gelen.url, "http://yerel");
    yanit.writeHead(200, { "content-type": "application/json" });
    if (u.pathname === "/olcut") { yanit.end(JSON.stringify({ istek, sorgu })); return; }
    istek += 1;
    const uye = u.searchParams.get("uye") ?? "U-1";
    const n = say.get(uye).n, liste = son.all(uye, SON); sorgu += 2;
    let geciken = -1;
    if (surum === "b") { geciken = gec.get(uye, GEC).n; sorgu += 1; }   // aday surumun ek alani
    yanit.end(JSON.stringify({ uye, odunc: n, geciken, liste }));
  }).listen(port);
}
```

Üreteç önceki derslerden gelir, iki farkla: ısınmayı **kendisi atmaz** ve hatalı istekleri
sayar. İkincisi zorunludur — bağlantısı reddedilen istek çok hızlı bitmiş görünür, yani
sayılmazsa ölü bir sunucu iyi bir ölçüme benzer.

```js
// kutuphane/olc.mjs — acik dongulu uretec. Gecikmeleri gonderim sirasina gore yazar, isinmayi
// disarida birakmaz. Argumanlar: <kol adi> <baglanti-noktasi> <tur> <hedef hiz> <sure s>
import { Agent, request } from "node:http";

const [kol, port, tur, hiz, sure] = process.argv.slice(2).map((x, i) => (i === 0 ? x : Number(x)));
const agent = new Agent({ keepAlive: true, maxSockets: 8192 });
let hata = 0;

function birIstek(ms, i) {
  const bas = performance.now();
  return new Promise((coz) => {
    const bit = () => { ms[i] = Math.round((performance.now() - bas) * 1000) / 1000; coz(); };
    const r = request({ port, path: `/odunc?uye=U-${i % 500}`, agent }, (y) => { y.resume(); y.on("end", bit); });
    r.on("error", () => { hata += 1; bit(); });   // hatali istek hizli gorunur, sayilir
    r.end();
  });
}

const olcut = () => new Promise((coz) => {
  request({ port, path: "/olcut", agent }, (y) => { let govde = "";
    y.on("data", (p) => { govde += p; }); y.on("end", () => coz(JSON.parse(govde))); }).end();
});

if (Number.isInteger(port) === false) {
  console.log("kullanim: node kutuphane/olc.mjs <kol> <baglanti-noktasi> <tur> <hedef hiz> <sure>");
} else {
  const ms = [], ucus = [];
  let borc = 0, sonT = 0, sira = 0;
  const bas = performance.now();
  while (performance.now() - bas < sure * 1000) {
    const t = (performance.now() - bas) / 1000;
    borc += hiz * (t - sonT); sonT = t;
    while (borc >= 1) { borc -= 1; ucus.push(birIstek(ms, sira++)); }
    await new Promise((c) => setTimeout(c, 5));
  }
  await Promise.all(ucus);
  const o = await olcut(), gecen = (performance.now() - bas) / 1000;
  console.log(JSON.stringify({ kol, tur, ulasilan: ms.length / gecen, hata, sorgu: o.sorgu / o.istek, ms }));
}
agent.destroy();
```

Tarama on turdan rastgele kümeler seçip kararı yeniden verir; üreteç doğrusal
eşleniktir ve tohumu görünürdür.

```js
// kutuphane/karsilastir.mjs — isinma, degiskenlik ve karar taramasi. Arguman: <jsonl>
import { readFileSync } from "node:fs";

const ESIK = 0.05, TOHUM = 20260731, DENEME = 500, ISINMA = 100, BLOK = 100;   // IO9, gorunur tohum
const s = readFileSync(process.argv[2], "utf8").trim().split("\n").map((x) => JSON.parse(x));
const v = (x, n = 2) => x.toFixed(n).replace(".", ",");
const g = (x, n) => String(x).padStart(n);
const yuz = (d, p) => { const t = [...d].sort((a, b) => a - b);
  return t[Math.min(t.length - 1, Math.floor((p / 100) * t.length))]; };
const TUR = [...new Set(s.map((r) => r.tur))];
const bul = (t, k) => s.find((r) => r.tur === t && r.kol === k);
const havuz = (k, bas, son) => s.filter((r) => r.kol === k).flatMap((r) => r.ms.slice(bas, son));
const P = (k) => yuz(havuz(k, ISINMA, 1e9), 95);

console.log(`${TUR.length} tur, kol basina tur basina ${Math.round(havuz("taban", 0, 1e9).length / TUR.length)} ` +
  `istek, ${s.reduce((a, r) => a + r.hata, 0)} hatali istek. Havuzda taban p95 ${v(P("taban"))} ms, denetim ` +
  `${v(P("denetim"))}, aday ${v(P("aday"))}; aday/taban ${v(P("aday") / P("taban"))} kat. Sorgu/istek: ` +
  `taban ${bul(TUR[0], "taban").sorgu}, aday ${bul(TUR[0], "aday").sorgu}.`);

console.log(`\nisinma: butun turlerin ayni istek bloklari havuzda, blok ${BLOK} istek`);
console.log("istek blogu  taban ortanca  taban p95(ms)  aday p95(ms)");
for (let i = 0; i < 5; i += 1) {
  const x = i * BLOK, y = i < 4 ? x + BLOK : 1e9, d = havuz("taban", x, y);
  console.log((i < 4 ? `${x}-${x + BLOK - 1}` : `${x}+`).padEnd(13) + g(v(yuz(d, 50), 3), 13) +
    g(v(yuz(d, 95)), 15) + g(v(yuz(havuz("aday", x, y), 95)), 14));
}

const p95 = (t, k) => yuz(bul(t, k).ms.slice(ISINMA), 95);
console.log("\ntur basina okuma: her turdan bir sayi, ilk 100 istek atilarak");
console.log("olcum          en kucuk  ortanca  en buyuk  yayilim kat");
for (const ad of ["taban p95(ms)", "denetim/taban", "aday/taban"]) {
  const d = TUR.map((t) => (ad.includes("/") ? p95(t, ad.split("/")[0]) / p95(t, "taban") : p95(t, "taban")))
    .sort((m, n) => m - n);
  console.log(ad.padEnd(15) + g(v(d[0]), 8) + g(v(yuz(d, 50)), 9) + g(v(d[d.length - 1]), 10) +
    g(v(d[d.length - 1] / d[0]), 13));
}

let t = TOHUM;
const rast = () => { t = (1103515245 * t + 12345) % 2147483648; return t / 2147483648; };
const karistir = (n) => { const a = [...Array(n).keys()];
  for (let i = n - 1; i > 0; i -= 1) { const j = Math.floor(rast() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; }
  return a; };
const orta = (d, ix) => yuz(ix.map((i) => d[i]), 50);
const tara = (n, esik, x) => {
  const dizi = (k) => TUR.map((tt) => yuz(bul(tt, k).ms.slice(x), 95));
  const T = dizi("taban"), D = dizi("denetim"), A = dizi("aday");
  let kalma = 0, gecme = 0;
  for (let i = 0; i < DENEME; i += 1) {
    const ix = karistir(TUR.length).slice(0, n), kapi = orta(T, ix) * (1 + esik);
    if (orta(D, ix) > kapi) kalma += 1;            // denetim taban ile ayni kod: gerileme yok
    if (orta(A, ix) <= kapi) gecme += 1;           // aday istek basina bir sorgu daha calistirir
  }
  return `${g(kalma, 3)}/${g(gecme, 3)}`;
};

const ESIKLER = [0.02, ESIK, 0.10, 0.20];
console.log(`\nkarar taramasi: ${DENEME} deneme, tohum ${TOHUM}; hucre = yanlis kalma / yanlis gecme`);
console.log("tur/yan  pencere         " + ESIKLER.map((e) => g(`esik %${Math.round(e * 100)}`, 11)).join(""));
for (const [n, x] of [[1, ISINMA], [2, ISINMA], [3, ISINMA], [5, ISINMA], [8, ISINMA], [1, 0], [8, 0]]) {
  console.log(g(n, 7) + "  " + (x === 0 ? "isinma dahil" : "ilk 100 atildi").padEnd(16) +
    ESIKLER.map((e) => g(tara(n, e, x), 11)).join(""));
}
```

```bash
# olc.sh — on tur; her turda uc kol art arda kosar: taban, denetim (taban ile ayni kod)
# ve aday. Her kosum kendi sunucu sureciyle baslar.
rm -f sonuc.jsonl
for T in $(seq 1 10); do
  for KOL in "taban a 8855" "denetim a 8856" "aday b 8857"; do
    set -- $KOL
    node kutuphane/sunucu.mjs $3 $2 & SUNUCU=$!
    sleep 0.8
    node kutuphane/olc.mjs $1 $3 $T 500 1.4 >> sonuc.jsonl
    kill $SUNUCU
    wait $SUNUCU 2>/dev/null
  done
done
node kutuphane/karsilastir.mjs sonuc.jsonl
```

```
10 tur, kol basina tur basina 698 istek, 0 hatali istek. Havuzda taban p95 2,05 ms, denetim 1,84, aday 2,37; aday/taban 1,15 kat. Sorgu/istek: taban 2, aday 3.

isinma: butun turlerin ayni istek bloklari havuzda, blok 100 istek
istek blogu  taban ortanca  taban p95(ms)  aday p95(ms)
0-99                 0,579           2,42          2,06
100-199              1,027           1,79          2,44
200-299              1,050           2,12          2,44
300-399              1,001           2,12          2,22
400+                 0,953           2,13          2,38

tur basina okuma: her turdan bir sayi, ilk 100 istek atilarak
olcum          en kucuk  ortanca  en buyuk  yayilim kat
taban p95(ms)      1,70     1,86      3,43         2,02
denetim/taban      0,52     1,03      1,07         2,05
aday/taban         0,66     1,24      1,56         2,37

karar taramasi: 500 deneme, tohum 20260731; hucre = yanlis kalma / yanlis gecme
tur/yan  pencere             esik %2    esik %5   esik %10   esik %20
      1  ilk 100 atildi      310/ 62    167/ 62      0/194      0/193
      2  ilk 100 atildi      264/107    196/108      0/245      0/252
      3  ilk 100 atildi      175/ 43     62/ 58      0/ 92      0/130
      5  ilk 100 atildi      132/  0     16/ 16      0/ 53      0/114
      8  ilk 100 atildi      141/  0      0/  0      0/  0      0/140
      1  isinma dahil        309/112     86/110      0/113      0/235
      8  isinma dahil        142/  0      0/  0      0/  2      0/140
```

## Isınma Penceresi

**Isınma** (warm-up), bir sürecin ilk isteklerinin yerleşik durumdan başka bir maliyetle
karşılanmasıdır. Birinci tablo ilk yüz isteğin ortancasını 0,579 milisaniye, dört yüzüncü
istekten sonrasını 0,953 milisaniye okuyor: ilk blok ortancada **1,65 kat iyimser**. Aynı
bloğun p95'i ise 2,42, yerleşik pencerede 2,13 — kuyrukta ters yönde. Isınma iki şey birden
yapıyor: derleme ve önbellek dolumu birkaç isteği çok yavaşlatıyor, üretecin eşzamanlılığı
henüz kurulmadığı için kalan istekler boş kuyruk buluyor.

Asıl önemlisi son sütundur. İlk blokta aday/taban oranı 2,06/2,42, yani **0,85**; yerleşik
pencerede 2,38/2,13, yani 1,12. Isınma gerilemenin işaretini ters çeviriyor: o blokta aday
sürüm tabandan hızlı görünüyor. Pencere bu yüzden ölçümden okunur — düzenek ilk yüz isteği
atar, çünkü tablo ikinci bloktan itibaren yerleşikliği gösteriyor.

## Gürültü ve Tek Tura Bakan Karar

İkinci tablo aynı sayıyı on kez okuyor. Tabanın p95'i 1,70 ile 3,43 milisaniye arasında
**2,02 kat** yayılıyor. Kodda hiçbir şey değişmedi; değişen tek şey turun koştuğu andır.

İkinci satır düzeneğin kendi hatasını veriyor. Denetim kolu tabanla **aynı kodu** çalıştırır;
oranın her turda 1,00 olması beklenirken 0,52 ile 1,07 arasında geziniyor. Ortanca 1,03,
yani sistematik bir kayma yok; ama yayılım 2,05 kat, yani iki özdeş sürüm arasında tek bir
turda yüzde kırk sekizlik bir "fark" görülebiliyor.

Üçüncü satır gerçek farkı taşıyor: turların aday/taban ortancası 1,24, yayılım 0,66–1,56.
Havuz oranı 1,15'tir; turların ortancası turları eşitler, havuz yavaş turlara ağırlık verir.
**En küçük değer 1'in altındadır:** tabanın 3,43 milisaniye okuduğu turda aday sürüm, istek
başına bir sorgu daha çalıştırdığı hâlde tabandan **yüzde otuz dört hızlı** ölçüldü. Tek
tura bakan karar orada gerilemeyi kaçırmakla kalmaz, tersine okur.

## Kaç Tur, Hangi Eşik

Üçüncü tablo iki ekseni birlikte tarıyor: yan başına toplanan tur sayısı ve eşik. Her hücre
denetim kolunun yanlış kalmasını, aday kolunun yanlış geçmesini sayıyor.

Yüzde ikilik eşik hiçbir tur sayısında kurtarılamıyor: bir turda 310, sekiz turda hâlâ 141
yanlış kalma. Neden ortanca değil yayılımdır: denetim kolunun 1,07'ye çıkan okumaları
sekiz turun ortancasını da kapının üstüne taşıyor. Bir eşik, aynı kodun kendisiyle
karşılaştırıldığında ürettiği yayılımın altına inemez.

IO9'un yüzde beşi bu alt sınırın üstünde ve savunulabilir — ama tek turda değil. Bir turda
167 yanlış kalma ve 62 yanlış geçme var; üç turda 62/58, beş turda 16/16, sekiz turda
**0/0**. Aranan sayı budur: yüzde beşlik kapı, yan başına sekiz tur toplanmadan karar
taşımaz.

Üst sınırı son sütun veriyor. Yüzde yirmide yanlış kalma sıfır, ama yanlış geçme sekiz
turda bile 140: gerçek fark 1,24 kat olduğu için kapı ona fazla yaklaşıyor ve gerileme
yayımlanıyor. Eşik, ayırması istenen farkın belirgin altında kalmak zorundadır.

Isınma satırları tabloyu ikinci kez veriyor. Sekiz turda ve yüzde beşte iki pencere de
0/0; tek turda ısınmayı içeri almak yanlış geçmeyi 62'den 110'a çıkarıyor. Isınma kararı
ancak tur sayısı azken çeviriyor, hep aynı yönde: gerilemeyi gizler.

## Bedel ve Kaçan Sınıf

Bedelin koşuma bağlı yanı ortama bağlıdır: otuz koşum bu makinede yetmiş saniyenin altında
sürdü. Koşumdan bağımsız yanı sayılır — otuz sunucu süreci, süreç başına 80.000 satırlık
dolum (toplam 2,4 milyon satır), 20.939 gönderilen istek, sıfır hatalı istek ve tarama
tablosunda 28.000 yeniden verilmiş karar. Sürüm farkı da koşumdan bağımsız bir niceliğe
bağlanır: istek başına iki sorguya karşı üç sorgu.

Bu ölçümün göremediği iki sınıf var. Birincisi, tarama **rastgele** gürültüyü sayar,
**sistematik sapmayı** saymaz. Denetim kolunun ortancası 1,03 çıktığı için burada sapma
görünmedi; görünseydi tur eklemek onu küçültmezdi, çünkü ortalama yalnız rastgele bileşeni
giderir. Nedenini ayıracak olan da bu düzenek değil, kol sırasının turlar arasında
döndürülmesidir.

İkincisi eşiğin biçimidir. Oransal bir kapı, tabanı yavaş olan bir uçta büyük mutlak
gerilemeleri geçirir: yüzde beş burada 0,10 milisaniyedir, taban 200 milisaniye olsaydı
10 milisaniye olurdu. Kapı bu yüzden oranı değil, birinci dersin mutlak eşiğini de taşır.

## Özet

- Gürültü ölçülür, varsayılmaz: aynı kodun p95'i on turda 2,02 kat yayıldı, iki özdeş kolun
  oranı 0,52 ile 1,07 arasında gezindi.
- Isınma penceresi istek bloklarıyla okunur: ilk yüz istek ortancada 1,65 kat iyimser,
  p95'te yüksek; net etkisi aday/taban oranını 1,12'den 0,85'e çevirmektir.
- Tek tura bakan karar yanılır: on turun birinde aday/taban 0,66 ölçüldü ve bir sorgu daha
  çalıştıran sürüm tabandan hızlı göründü.
- Eşiğin alt sınırı düzeneğin kendi yayılımıdır: yüzde ikilik kapı sekiz turda bile 141
  yanlış kalma bıraktı; IO9'un yüzde beşi ancak sekiz turla 0/0'a indi.
- Eşiğin üst sınırı gerçek farktır: yüzde yirmilik kapı 1,24 katlık gerilemeyi sekiz turda
  bile 140 kez geçirdi.

## Sonraki Adım

Başarım testinin beş dersi bir eşiği kurdu, senaryoyu modelledi, ölçütü seçti, sonucu bir
katmana atfetti ve karşılaştırmayı disipline aldı. Beşinin de altında aynı varsayım duruyor:
istekler geçerliydi, niyet iyiydi. Yük üreteci gerçek bir okuyucunun yapabileceği şeyleri
hızlı yaptı; hiçbir koşum, sistemi bilerek kötüye kullanan bir istemcinin ne bulacağını
sormadı. Sonraki ders bu soruyu en ucuz sorulduğu yerde, programın kaynak metninde ele alır
ve yine sınamanın kendisini ölçer: kaç kusur bulunuyor, kaç uyarı boşa çıkıyor, hangi kusur
hiçbir eşikte görünmüyor.
