package main

import (
	"bytes"
	"fmt"
	"runtime"
	"runtime/debug"
	"sync"
)

// sink* mencegah compiler menghapus alokasi yang hasilnya disimpan.
var (
	sinkString string
	sinkInts   []int
	sinkBuffer *bytes.Buffer
)

// ukurAlokasi menjalankan f di antara dua pembacaan runtime.ReadMemStats,
// sehingga hanya alokasi yang terjadi di dalam f yang ikut dihitung.
func ukurAlokasi(f func()) (alokasi int64, ukuran int64) {
	var sebelum, sesudah runtime.MemStats
	runtime.ReadMemStats(&sebelum)
	f()
	runtime.ReadMemStats(&sesudah)
	return int64(sesudah.Mallocs - sebelum.Mallocs),
		int64(sesudah.TotalAlloc - sebelum.TotalAlloc)
}

// buatAngka menyiapkan data uji tetap di luar jendela pengukuran.
func buatAngka(n int) []int {
	xs := make([]int, 0, n)
	for i := 0; i < n; i++ {
		xs = append(xs, i)
	}
	return xs
}

func main() {
	// GC dimatikan sementara agar pengukuran murni:
	// sync.Pool dibersihkan saat siklus GC, dan siklus tak terduga
	// akan membuat hasil berubah antar eksekusi.
	gogcLama := debug.SetGCPercent(-1)
	defer debug.SetGCPercent(gogcLama)

	// Pola 1: penyambungan string di dalam loop.
	alokasi, ukuran := ukurAlokasi(func() {
		s := ""
		for i := 0; i < 100; i++ {
			s += "belajar go "
		}
		sinkString = s
	})
	fmt.Printf("Penyambungan += 100 kali       : %d alokasi, %d byte\n", alokasi, ukuran)

	// Pola 2a: filter yang menumbuhkan slice hasil dengan append.
	asal := buatAngka(500)
	alokasi, ukuran = ukurAlokasi(func() {
		hasil := []int{}
		for _, v := range asal {
			if v%2 == 0 {
				hasil = append(hasil, v)
			}
		}
		sinkInts = hasil
	})
	fmt.Printf("Filter tanpa kapasitas         : %d alokasi, %d byte\n", alokasi, ukuran)

	// Pola 2b: filter yang sama dengan prealokasi kapasitas.
	alokasi, ukuran = ukurAlokasi(func() {
		hasil := make([]int, 0, len(asal))
		for _, v := range asal {
			if v%2 == 0 {
				hasil = append(hasil, v)
			}
		}
		sinkInts = hasil
	})
	fmt.Printf("Filter dengan make(cap)        : %d alokasi, %d byte\n", alokasi, ukuran)

	// Pola 3: konversi []byte menjadi string berulang kali.
	potongan := []byte("belajar-pemrograman")
	alokasi, ukuran = ukurAlokasi(func() {
		for i := 0; i < 1000; i++ {
			sinkString = string(potongan)
		}
	})
	fmt.Printf("Konversi []byte->string 1000x  : %d alokasi, %d byte\n", alokasi, ukuran)

	// Pola 4: sync.Pool memakai ulang buffer yang sudah dikembalikan.
	pool := &sync.Pool{
		New: func() any { return new(bytes.Buffer) },
	}
	pertama := pool.Get().(*bytes.Buffer) // alokasi pertama melalui New
	pool.Put(pertama)                     // objek dikembalikan ke pool
	alokasi, ukuran = ukurAlokasi(func() {
		sinkBuffer = pool.Get().(*bytes.Buffer) // dipakai ulang, bukan baru
	})
	fmt.Printf("Pool Get setelah Put           : %d alokasi, %d byte\n", alokasi, ukuran)
	sinkBuffer.WriteString("contoh")
}
