Belajar Golang - Performansi & Optimasi Go
Episode 13 of 19

Belajar Golang - Performansi & Optimasi Go

Episode ini mengukur dan mengoptimalkan performa Go: profiling dengan pprof, benchmarking dengan go test -bench, analisis allocation dengan escape analysis, serta teknik tuning seperti buffer reuse dan zero allocation patterns.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Episode 13 menjawab satu pertanyaan: apakah aplikasi Go kalian cepat? Bukan dengan tebakan, melainkan dengan pengukuran. Go menyediakan tooling profiling dan benchmarking kelas dunia langsung dari toolchain resminya.

Kalian akan belajar empat hal: profiling CPU dan memori dengan pprof, benchmarking dengan go test -bench, memahami alokasi memori dan escape analysis, serta teknik performance tuning seperti buffer reuse dan zero allocation patterns. Fokusnya selalu: ukur dulu, optimalkan yang berdampak.

Profiling dengan pprof

Menyalakan Profiling

net/http/pprof menyediakan endpoint profiling untuk aplikasi yang berjalan. Import blank cukup untuk mengaktifkannya di server HTTP.

Aktifkan pprof
package main
 
import (
	"net/http"
	_ "net/http/pprof"
)
 
func main() {
	http.ListenAndServe(":8080", nil)
}

Saat aplikasi berjalan, akses /debug/pprof/ untuk daftar profil, /debug/pprof/profile untuk CPU profile selama 30 detik, dan /debug/pprof/heap untuk heap snapshot. Jangan aktifkan pprof di endpoint publik tanpa autentikasi.

Menganalisis Profil

Kumpulkan profil lalu analisis dengan go tool pprof:

Kumpulkan dan analisis profil
curl -o cpu.prof http://localhost:8080/debug/pprof/profile?seconds=30
go tool pprof cpu.prof

Di dalam shell interaktif, perintah top menampilkan fungsi yang paling banyak memakai CPU, list namaFungsi menunjukkan per baris, dan web memvisualisasikan grafik call. Setelah melihat profil, optimalkan hanya fungsi yang benar-benar dominan.

Benchmark dengan go test

Membangun Benchmark yang Benar

Benchmark (dari episode 6) menjadi dasar perbandingan. Saat mengoptimalkan, ukur kondisi sebelum dan sesudah, lalu bandingkan:

Benchmark dua implementasi
func BenchmarkGabungBuffer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var sb strings.Builder
		for j := 0; j < 100; j++ {
			sb.WriteString("data")
		}
		_ = sb.String()
	}
}

Jalankan dengan go test -bench=. -benchmem -count=5 ./... untuk hasil yang lebih stabil. -count mengulang benchmark agar noise jaringan dan OS bisa dirata-ratakan.

Mencegah Compiler Optimasi

Compiler Go bisa menghilangkan kode yang hasilnya tidak terpakai. Setiap hasil benchmark sebaiknya disimpan ke package-level variable agar tidak dioptimalkan.

Simpan hasil agar tidak dioptimasi
var hasilBenchmark string
 
func BenchmarkKode(b *testing.B) {
	var s string
	for i := 0; i < b.N; i++ {
		s = gabungkan()
	}
	hasilBenchmark = s
}

Escape Analysis dan Alokasi

Kapan Object Dialokasikan ke Heap

Escape analysis menentukan apakah sebuah variabel hidup di stack (murah) atau heap (mahal, butuh GC). Variabel yang "melarikan diri" ke luar fungsi — dikembalikan, disimpan di pointer, atau ditangkap closure — dialokasikan ke heap.

Melihat escape analysis
func buatPointer() *int {
	n := 42
	return &n
}

Jalankan go build -gcflags="-m" main.go untuk melihat keputusan compiler. Jika output menyebut moved to heap, objek dialokasikan di heap. Mengurangi alokasi heap menurunkan beban garbage collector secara langsung. Alternatifnya, go build -gcflags="-m" main.go tetap menghasilkan binary meskipun menampilkan analisis alokasi.

Allocation dan Latency

Setiap alokasi heap menambah kerja GC: semakin banyak alokasi, semakin sering GC berjalan. Itulah mengapa pola zero allocation di kode hot path bisa meningkatkan throughput secara signifikan.

Teknik Performance Tuning

Reuse Buffer

Untuk pembacaan data yang berulang, alokasikan buffer sekali lalu pakai berulang kali. sync.Pool bahkan memungkinkan buffer dibagi antar goroutine:

Pool buffer
var bufPool = sync.Pool{
	New: func() any {
		return make([]byte, 1024)
	},
}
 
func proses(data []byte) {
	buf := bufPool.Get().([]byte)
	defer bufPool.Put(buf)
	copy(buf, data)
	// proses data
}

sync.Pool mengembalikan objek yang pernah dibuat dan membuangnya saat GC berjalan. Cocok untuk buffer sementara, bukan untuk state yang harus bertahan.

Slice dengan Kapasitas yang Tepat

append yang berulang memicu reallocation ketika kapasitas penuh. Beri tahu kapasitas awal saat membuat slice agar alokasi cukup sekali:

Alokasi kapasitas awal
hasil := make([]int, 0, 1000)
for _, v := range sumber {
	hasil = append(hasil, v)
}

Dengan make([]int, 0, 1000), kapasitas dicadangkan di awal. Untuk ribuan elemen, ini menghindari puluhan reallocation yang tidak perlu.

Mengurangi Garbage

Beberapa praktik lain: reuse struct, hindari pointer ke data kecil, dan gunakan range atas value untuk data immutable. Jangan pernah mengoptimalkan sebelum profil menunjukkan masalah — optimasi prematur hanya menambah kompleksitas.

Penutup

Episode 13 membekali kalian siklus pengukuran dan optimasi: profiling dengan pprof dan go tool pprof, benchmark dengan go test -bench dan -benchmem, pemahaman escape analysis dan alokasi heap, serta teknik buffer reuse, sync.Pool, dan kapasitas slice yang tepat.

Inti yang harus dibawa pulang:

  • Ukur dulu dengan pprof sebelum mengoptimalkan apa pun.
  • Benchmark memakai -benchmem dan -count untuk hasil stabil.
  • Escape analysis menentukan variabel dialokasikan di stack atau heap.
  • Kurangi alokasi heap untuk menekan beban garbage collector.
  • sync.Pool me-reuse buffer lintas goroutine.
  • Cadangkan kapasitas slice agar append tidak sering reallocate.

Di episode 14 selanjutnya kita akan membahas observability, logging, dan tracing — logging terstruktur dengan zap, logrus, atau zerolog, tracing terdistribusi dengan OpenTelemetry, dan metrik terstruktur dengan Prometheus client. Aplikasi kalian akan bisa diintrospeksi saat berjalan di production.

Belajar Golang - Performansi & Optimasi Go | Belajar Golang