---
title: 'Çöp Toplama Davranışı'
source: 'https://academia.sh/tr/kurslar/go-uretim/cop-toplama-davranisi'
course: 'Üretim Go: Test, Araçlar ve Başarım'
language: tr
updated: '2026-09-13T05:08:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Önceden yer ayırmanın, arabellek yeniden kullanımının ve değer/işaretçi seçiminin ayırma sayısına etkisini beş koşumda ölçer'
    - 'Aynı ayırma sayısına biri okunaklı biri okunaksız iki farklı yazımla ulaşılabildiğini koşturarak gösterir'
    - 'Ayırma sayısını düşürmenin, kaçış çözümlemesindeki kararı otomatik olarak iyileştirmediğini ayırt eder'
---

# Çöp Toplama Davranışı

Ayırma baskısının azalması sayıyla ölçülüyor: önceden yer ayırma, arabellek yeniden kullanımı ve değer/işaretçi seçimi aynı işin ayırma sayısını dörtten bire, dörtten sıfıra, birden altmış beşe taşıyor. Sınırlayıcı ölçüm: bir sayının düşmesi her zaman bir iyileştirme değil — okunabilirliği bozan bir yazım da aynı sayıya ulaşabiliyor.

Önceki ders bir değerin öbeğe **taşınıp taşınmadığını**, derleme anında verilen tek seferlik
bir karar olarak ölçtü. Ama bir program bir kez değil binlerce kez çalışıyor ve öbeğe giden
her değer bir gün oradan da kalkması gerekiyor. Bu ders o kalkışın yükünü sorguluyor: aynı
işi yapan farklı yazımlar, toplamda kaç değeri öbeğe gönderiyor ve bu sayı belirli
tekniklerle nasıl düşüyor?

Çöp toplamanın kendisi — erişilebilirlik, kök kümesi, işaretle ve süpür adımları,
duraklatmanın yaşayan nesneyle orantılı oluşu — İşletim Sistemi Kavramları kursunda
(M01/K05) kavram olarak kuruldu ve burada tekrarlanmıyor. Bu ders kuramı değil **sonucu**
ölçüyor: öbeğe daha az değer göndermek, toplayıcının daha az iş yapması anlamına geliyor.
Duraklatma süresi, geri kazanılan bellek miktarı ve nesil boyutu bu derste hiç yazılmıyor —
hepsi ortama bağlı; sayılan tek şey **ayırma sayısı**.

Bu daraltmanın gerekçesi önceki kurstan geliyor: o kurs, toplayıcının işinin yaşayan
nesnelerle orantılı olduğunu kurmuştu — ne kadar çok nesne öbekte duruyorsa taraması o kadar
uzun sürüyor. Bu ders o ilişkinin girdi ucuna bakıyor: bir program öbeğe **daha az** nesne
gönderirse, tarayacak daha az şey oluyor. Aradaki bağ dolaylı — bu ders duraklatmayı hiç
ölçmüyor — ama yön açık: ayırma sayısı düşerse toplayıcının işi de düşme eğiliminde.
Bu dersin ölçtüğü, o düşüşün girdi tarafı; toplayıcının bunu nasıl işlediği önceki kursun
konusu.

## Ayırma Baskısı: Üç Teknik, Bir Ölçüm

"Ayırma baskısı" bir programın birim zamanda öbeğe ne kadar yeni değer gönderdiğini
anlatıyor. Baskı yüksekse toplayıcı daha sık çalışmak zorunda kalıyor; baskı düşükse daha
seyrek. Bu ders baskıyı süre üzerinden değil, **sayı** üzerinden ölçüyor: aynı işin bir
kere çalıştırılması kaç değer ayırıyor. Üç teknik bu sayıyı düşürüyor — önceden yer
ayırma, arabellek yeniden kullanımı, değer/işaretçi seçimi — ve dördüncü bir örnek bu
sayının tek başına yeterli olmadığını gösteriyor.

Dördü de aynı ölçüm kalıbını paylaşıyor: bir işlev tanımlanıyor, o işlevi çağıran bir
karşılaştırmalı ölçüm işlevi yazılıyor ve bu ölçüm beş kez koşturulup kaç ayrı değer
geldiği sayılıyor. Önceki iki dersten farklı olarak burada tek bir değer değil, dört
ayrı karşılaştırma yan yana duruyor — her biri kendi çiftiyle (ham/ayrılmış,
yeni/yeniden, değer/işaretçi) bir öncesi ve sonrasını gösteriyor.

```go
// olcum.go — uc teknigin ayirma sayisina etkisi, bes kosumda
package main

import (
	"fmt"
	"strconv"
	"testing"
)

type Kayit struct {
	Ad     string
	Miktar int
}

const kayitSayisi = 64

var toplamYut, uzunlukYut, kayitYut int

// --- a) onceden yer ayirma ---

//go:noinline
func topluOlustur(n int) []int {
	var d []int
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

//go:noinline
func topluOlusturAyrilmis(n int) []int {
	d := make([]int, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

func benchTopluHam(b *testing.B) {
	for i := 0; i < b.N; i++ {
		toplamYut += len(topluOlustur(kayitSayisi))
	}
}
func benchTopluAyrilmis(b *testing.B) {
	for i := 0; i < b.N; i++ {
		toplamYut += len(topluOlusturAyrilmis(kayitSayisi))
	}
}

// --- b) arabellek yeniden kullanimi ---

//go:noinline
func satirOlustur(n int) []byte {
	var buf []byte
	for i := 0; i < n; i++ {
		buf = append(buf, strconv.Itoa(i)...)
		buf = append(buf, ' ')
	}
	return buf
}

//go:noinline
func satirOlusturYeniden(buf []byte, n int) []byte {
	buf = buf[:0]
	for i := 0; i < n; i++ {
		buf = append(buf, strconv.Itoa(i)...)
		buf = append(buf, ' ')
	}
	return buf
}

var paylasilanArabellek = make([]byte, 0, 8)

func benchArabellekYeni(b *testing.B) {
	for i := 0; i < b.N; i++ {
		uzunlukYut += len(satirOlustur(16))
	}
}
func benchArabellekYeniden(b *testing.B) {
	for i := 0; i < b.N; i++ {
		paylasilanArabellek = satirOlusturYeniden(paylasilanArabellek, 16)
		uzunlukYut += len(paylasilanArabellek)
	}
}

// --- c) deger / isaretci secimi ---

//go:noinline
func degerDilimiOlustur(n int) []Kayit {
	d := make([]Kayit, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, Kayit{Ad: "x", Miktar: i})
	}
	return d
}

//go:noinline
func isaretciDilimiOlustur(n int) []*Kayit {
	d := make([]*Kayit, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, &Kayit{Ad: "x", Miktar: i})
	}
	return d
}

func benchDegerDilimi(b *testing.B) {
	for i := 0; i < b.N; i++ {
		kayitYut += len(degerDilimiOlustur(kayitSayisi))
	}
}
func benchIsaretciDilimi(b *testing.B) {
	for i := 0; i < b.N; i++ {
		kayitYut += len(isaretciDilimiOlustur(kayitSayisi))
	}
}

// --- d) karisik yazim: ayni sayiya baska bir yoldan ulasiyor ---

//go:noinline
func topluOlusturKarisik(n int) []int {
	kapasite := 0
	for sayac := 0; sayac < n; sayac++ {
		kapasite++
	}
	d := make([]int, 0, kapasite)
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

func benchTopluKarisik(b *testing.B) {
	for i := 0; i < b.N; i++ {
		toplamYut += len(topluOlusturKarisik(kayitSayisi))
	}
}

func besKosum(f func(*testing.B)) (int64, int) {
	degerler := map[int64]bool{}
	var ilk int64
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(f)
		degerler[r.AllocsPerOp()] = true
		if i == 0 {
			ilk = r.AllocsPerOp()
		}
	}
	return ilk, len(degerler)
}

func satir(ad string, f func(*testing.B)) {
	ayirma, ayriDeger := besKosum(f)
	fmt.Printf("%-30s ayirma=%-3d bes kosumda ayri deger=%d\n", ad, ayirma, ayriDeger)
}

func main() {
	fmt.Println("-- ayirma baskisi: teknik ayirma sayisini nasil degistiriyor --")
	satir("onceden ayrilmamis dilim", benchTopluHam)
	satir("onceden ayrilmis dilim", benchTopluAyrilmis)
	satir("her cagrida yeni arabellek", benchArabellekYeni)
	satir("yeniden kullanilan arabellek", benchArabellekYeniden)
	satir("deger dilimi", benchDegerDilimi)
	satir("isaretci dilimi", benchIsaretciDilimi)
	satir("karisik yazim (ayni sayi)", benchTopluKarisik)
}
```

```
-- ayirma baskisi: teknik ayirma sayisini nasil degistiriyor --
onceden ayrilmamis dilim       ayirma=4   bes kosumda ayri deger=1
onceden ayrilmis dilim         ayirma=1   bes kosumda ayri deger=1
her cagrida yeni arabellek     ayirma=4   bes kosumda ayri deger=1
yeniden kullanilan arabellek   ayirma=0   bes kosumda ayri deger=1
deger dilimi                   ayirma=1   bes kosumda ayri deger=1
isaretci dilimi                ayirma=65  bes kosumda ayri deger=1
karisik yazim (ayni sayi)      ayirma=1   bes kosumda ayri deger=1
```

**BS5.** Yedi işlevin hepsi `//go:noinline` ile işaretlendi — dilin kendi derleme
yönergesi, derleyiciye bu işlevi çağrıldığı yere açmamasını söylüyor. Bunun nedeni önceki
dersle aynı: bir işlev çağrıldığı yere açılırsa dönen değerin çağıran tarafından hemen
kullanılıp atıldığı görülebiliyor ve bu, karşılaştırmanın ölçmek istediği ayırma davranışını
gizleyebiliyor. Yönerge, dört tekniğin ayırma sayısını **kendi gövdesinde**, herhangi bir
çağırana karışmadan ölçmeyi sağlıyor.

`toplamYut`, `uzunlukYut` ve `kayitYut` paket düzeyinde üç değişken olarak duruyor ve her
karşılaştırmalı ölçüm işlevi kendi döndürdüğü değeri bunlardan birine yazıyor. Bunun
amacı sonucu "kullanmak": bir işlevin dönüş değeri hiçbir yere yazılmazsa derleyici o
çağrının tamamını gereksiz sayıp kaldırabiliyor ve o zaman ölçülen şey işlevin kendisi
değil boş bir döngü olurdu. Sonucu bir pakete yazmak bu riski kaldırıyor — çağrı gerçekten
oluyor ve gerçekten ayırıyor.

## Sayıların Okunması

**Önceden yer ayırma (a):** `topluOlustur`, boş bir dilime tek tek ekleyerek 64 elemana ulaşıyor;
dilim her kapasitesi dolduğunda büyüyor ve her büyüme bir yeniden ayırma. Bu büyüme dört kez
tetikleniyor. `topluOlusturAyrilmis` aynı 64 elemanı, `make([]int, 0, 64)` ile baştan doğru
kapasitede açılan bir dilime ekliyor — büyüme hiç tetiklenmiyor; tek ayırma yalnız ilk `make`
çağrısına ait. Dört ayırmadan bire düşüş, kapasitenin **önceden bilindiği** durumlarda büyümenin
gereksiz olduğunu gösteriyor.

**Arabellek yeniden kullanımı (b):** `satirOlustur` her çağrıda sıfırdan başlayan bir
`[]byte` açıyor ve 16 parçayı buna ekleyerek dört kez büyütüyor. `satirOlusturYeniden` ise
paket düzeyinde saklanan tek bir arabelleği her çağrıda `buf[:0]` ile sıfır uzunluğa
indirgeyip yeniden dolduruyor; kapasite önceki çağrılardan zaten yeterli boyuta ulaştığı
için yeni bir ayırma hiç gerekmiyor. Dörtten sıfıra düşüş, önceki tekniktekinden daha
keskin — çünkü burada yalnız büyüme sayısı değil, **her çağrıdaki ilk ayırma** da ortadan
kalkıyor.

Bu sıfır sayısının bir önkoşulu var: arabelleğin kapasitesi, art arda gelen çağrıların
hiçbirinde aşılmıyor. `paylasilanArabellek` sekiz baytlık bir kapasiteyle açılıyor ama ilk
birkaç çağrı sırasında kendiliğinden büyüyor, çünkü `satirOlusturYeniden` da `append`
kullanıyor ve kapasite yetmediğinde büyüme kaçınılmaz. Beş koşumun beşinde de sıfır
çıkması, bu büyümenin **ilk çağrıda bir kez** olup sonrasında hiç tekrarlanmadığını
gösteriyor — kapasite bir kere yeterli boyuta ulaştıktan sonra aynı boyuttaki her çağrı
onu yeniden kullanabiliyor. Girdi boyutu değişken olsaydı (bazen 16, bazen 1600 parça)
büyüme tekrar tekrar tetiklenir ve sıfıra düşüş kaybolurdu.

**Değer/işaretçi seçimi (c):** `degerDilimiOlustur`, 64 `Kayit` değerini **tek bir**
bitişik dilimde tutuyor — tüm elemanlar aynı ayırmanın parçası, toplam ayırma sayısı bir.
`isaretciDilimiOlustur` ise her elemanı `&Kayit{...}` ile ayrı ayrı öbeğe gönderiyor: bir
ayırma dilimin kendisi için, altmış dört ayırma da elemanların her biri için — toplam
altmış beş. Aradaki fark iki tekniktekinden çok daha büyük, çünkü burada değişen şey
büyüme kalıbı değil, **kaç ayrı nesnenin** var olduğu.

Bu fark önceki dersle doğrudan bağlanıyor: `&Kayit{...}` yazımı, önceki derste ölçülen
`adresDonduren` kalıbının aynısı — bir değerin adresini almak onu öbeğe itiyor. Buradaki
fark ölçek: önceki derste tek bir değerin kaderi ölçülüyordu, burada aynı karar altmış
dört kez tekrarlanıyor ve etkisi katlanarak büyüyor. Bir yapı dilimi seçmek yerine bir
işaretçi dilimi seçmek, gerçek kod tabanlarında sık görülen bir karar — genelde büyük
yapıları kopyalamaktan kaçınmak için. Bu ölçüm o kazancın bir bedeli olduğunu gösteriyor:
kopyalamaktan kaçınmak, ayırma sayısını artırıyor. Hangi bedelin hangi kazanca değdiği bu
dersin ölçemediği bir soru — yapının boyutu, kopyalama sıklığı ve kalan referans sayısı gibi
etkenlere bağlı ve durumdan duruma değişiyor.

## Üç Tekniğin Ortak Noktası

Üç tekniğin de dayandığı ortak bir düşünce var: **bir kez yeterince büyük ayırmak, çok
kez küçük küçük ayırmaktan daha az iş çıkarıyor**. Önceden yer ayırma bunu kapasiteyi
baştan biliyorsa uyguluyor; arabellek yeniden kullanımı bunu kapasiteyi bir kez öğrenip
tekrar tekrar kullanarak uyguluyor; değer dilimi bunu elemanları ayrı ayrı değil bitişik
tutarak uyguluyor. Üçü de aynı yöne itiyor — ayrı ayrı ayırma sayısını azaltmak — ama
üçünün önkoşulu farklı: biri kapasitenin **önceden bilinmesini**, biri arabelleğin
**paylaşılabilir olmasını**, biri elemanların **bitişik durabilmesini** gerektiriyor. Bu
önkoşullardan biri sağlanmıyorsa teknik uygulanamıyor — kapasite önceden bilinmiyorsa
`make`e ne yazılacağı belirsiz kalıyor, arabellek eşzamanlı erişiliyorsa paylaşmak yarış
koşuluna yol açıyor, elemanlar farklı yaşam sürelerinde tutuluyorsa bitişik dilim onları
gereksiz yere bir arada tutuyor.

## Sınırlayıcı Ölçüm: Bir Sayının Düşmesi Otomatik Bir İyileştirme Değil

Dördüncü satır, `topluOlusturKarisik`, `topluOlusturAyrilmis` ile **aynı** sayıya —
bire — ulaşıyor. Ama iki işlevin gövdesi aynı değil: `topluOlusturKarisik` kapasiteyi
doğrudan `n` olarak almak yerine, `n` kadar dönen ayrı bir sayaç döngüsüyle **yeniden
hesaplıyor** ve sonucu `kapasite` adlı bir değişkene yazıyor. Bu döngü hiçbir yeni bilgi
üretmiyor — `kapasite` her zaman `n`e eşit çıkıyor — ama kaynağı okuyan birine "neden
ayrı bir hesaplama var, `n` yetmiyor mu" sorusunu sorduruyor.

**BS6.** Bu, kursun en önemli uyarılarından biri: iki yazım aynı ayırma sayısına ulaştı,
ama biri okunaklı biri değil. Ayırma sayısı tek başına hangi kodun **daha iyi** olduğunu
söylemiyor — yalnız kaç değerin öbeğe gittiğini söylüyor. `topluOlusturKarisik`daki
gereksiz döngü hiçbir ayırma eklemedi, çünkü yalnız bir tam sayı üzerinde çalışıyor
(tam sayılar bu boyutta yığıtta kalıyor); ama okunabilirliği bozdu ve kodu okuyanın zaman
kaybetmesine yol açtı. Bir sayı düştüğünde ya da sabit kaldığında sorulması gereken soru
"bu değişiklik neyin karşılığında geldi" — yalnızca "sayı ne oldu" değil.

Bu sınır, kaçış çözümlemesi dersindeki sınırla da bağlanıyor: bir değerin öbeğe gitmesi
kararı derleyicinin kararıydı, kodun niyeti değildi. Burada da benzer bir ayrım var —
ayırma sayısını düşürmek için yazılan kod, sayıyı gerçekten düşürebilir ama bunun **neden**
düştüğünü açıklamıyor. `topluOlusturKarisik`ı okuyan biri, sayının düşük olmasının
nedeninin `make`in doğru kapasiteyle çağrılması olduğunu, döngünün kendisinin hiçbir işe
yaramadığını yalnız kodu satır satır izleyerek anlayabiliyor — ayırma sayısının kendisi bu
ayrımı yapmıyor.

Bu örnek uydurma değil, gerçek kod tabanlarında görülen bir kalıbın küçültülmüş hâli: bir
geliştirici ayırma sayısını düşürmek için bir değişiklik yapıyor, sayı gerçekten düşüyor
ve değişiklik "başarılı" sayılıyor — ama sayıyı düşüren şeyin değişikliğin **hangi**
parçası olduğu hiç sorgulanmıyor. `topluOlusturKarisik` bu tuzağı somutlaştırıyor:
gereksiz döngü kalsa da, kaldırılsa da ayırma sayısı bir olarak kalıyor, çünkü sayıyı
belirleyen `make`in kapasite argümanı, döngü değil. Ölçüm aracı bu ayrımı yapmıyor; ayrımı
yapan, kodu okuyup hangi satırın hangi sayıya karşılık geldiğini izleyen kişi.

## Özet

- Önceden yer ayırma, ayırma sayısını dörtten bire düşürdü: kapasite önceden biliniyorsa
  dilimin büyüme adımları gereksiz.
- Arabellek yeniden kullanımı, ayırma sayısını dörtten sıfıra düşürdü: paylaşılan bir
  arabelleği sıfırlayıp yeniden doldurmak, her çağrıda yeni bir ayırma gerektirmiyor.
- Değer dilimi bir ayırmada kaldı, işaretçi dilimi altmış beşe çıktı: eleman sayısı kadar
  ayrı nesne açmak, tek bir bitişik depoda tutmaktan çok daha pahalı.
- Aynı ayırma sayısına (bir) iki farklı yoldan ulaşıldı — biri doğrudan doğru kapasiteyle
  `make` çağıran okunaklı yazım, öbürü gereksiz bir hesaplama ekleyen okunaksız yazım.
- Ayırma sayısının düşmesi ya da sabit kalması tek başına bir iyileştirme kanıtı değil;
  hangi değişikliğin sayıyı etkilediği kodun kendisinden okunmalı. Ayırma sayısı bir
  **koşum** bilgisidir — üç yazımın üçü de yalnız koşturularak ayrıldı.
- Üç tekniğin ortak dayanağı aynı: bir kez yeterince büyük ayırmak, çok kez küçük küçük
  ayırmaktan daha az iş çıkarıyor; her tekniğin kendi önkoşulu (bilinen kapasite,
  paylaşılabilir arabellek, bitişik durabilen eleman) sağlanmadan uygulanamıyor.

## Sonraki Adım

Bu ders bir işlevin **kaç kez** çağrıldığında kaç değer ayırdığını sayıyor bilgisiyle
sınırlı kaldı — bir programın yalnız bellek yönü. Ama gerçek bir programın çalışma zamanı
bundan daha fazlasını içeriyor: hangi goroutine ne zaman başladı, kaç tanesi eşzamanlı
çalıştı, hangi olay hangi sırayla oldu — bunların hiçbiri bir ayırma sayısıyla
yakalanmıyor. Sıradaki ders bu daha geniş kayda bakıyor — yürütme izinin hangi olayları
saydığı ve iki farklı kurulumun ürettiği olay sayılarının nasıl karşılaştırıldığı.
