Optimasi yang dimulai dari intuisi hampir selalu salah alamat: loop yang terlihat mencurigakan sering hanya 2% dari total waktu, sementara pembunuh sebenarnya bersembunyi di fungsi yang tampak polos. CPU profile menghilangkan tebakan — perekam mengambil sampel stack secara berkala selama program berjalan, sehingga fungsi yang paling sering muncul di sampel adalah fungsi yang paling banyak memakan waktu prosesor. Pada chapter ini kita menjalankan dua beban kerja berbeda dalam satu jendela perekaman, membandingkan kontribusinya lewat go tool pprof -top, lalu menajamkan hasilnya sampai ke baris kode dengan -list.
Program dengan Dua Beban Kerja #
Berkas contoh.go di bawah halaman menggabungkan dua fungsi berat dengan karakter berbeda: jumlahKuadratMod berisi operasi aritmetika tanpa percabangan, sedangkan hitungPrima menguji pembagian berulang untuk setiap kandidat bilangan prima. Keduanya dijalankan di antara StartCPUProfile dan StopCPUProfile:
package main
import (
"fmt"
"os"
"runtime/pprof"
)
// jumlahKuadratMod menjumlahkan (i*i)%101 untuk i dari 0 sampai n-1.
func jumlahKuadratMod(n int) int64 {
var total int64
for i := range n {
x := int64(i)
total += (x * x) % 101
}
return total
}
// hitungPrima menghitung banyaknya bilangan prima di bawah batas
// dengan uji pembagian terhadap setiap kandidat.
func hitungPrima(batas int) int {
jumlah := 0
for n := 2; n < batas; n++ {
prima := true
for d := 2; d*d <= n; d++ {
if n%d == 0 {
prima = false
break
}
}
if prima {
jumlah++
}
}
return jumlah
}
func main() {
berkas, err := os.CreateTemp("", "cpu-*.prof")
if err != nil {
fmt.Println("gagal membuat berkas profil:", err)
return
}
if err := pprof.StartCPUProfile(berkas); err != nil {
fmt.Println("gagal memulai profil CPU:", err)
return
}
hasilKuadrat := jumlahKuadratMod(300_000_000)
hasilPrima := hitungPrima(1_500_000)
pprof.StopCPUProfile()
berkas.Close()
fmt.Println("jumlah kuadrat mod:", hasilKuadrat)
fmt.Println("prima di bawah 1500000:", hasilPrima)
fmt.Println("profil CPU ditulis: true")
}Output yang Dihasilkan:
jumlah kuadrat mod: 14999999855
prima di bawah 1500000: 114155
profil CPU ditulis: trueMari kita telaah baris demi baris program di atas:
- Baris 10–17 (
jumlahKuadratMod): loop 300 juta iterasi dengan operasi tetap; hasilnya (14999999855) konstan sehingga beban kerja ini bisa dibandingkan antar eksekusi. - Baris 21–36 (
hitungPrima): uji pembagian sampai akar kandidat; loop dalam (baris 25–30) membuat pola akses memori dan percabangan yang jauh lebih padat. - Baris 39–43: berkas profil dibuat lewat
os.CreateTempdengan polacpu-*.prof; path pastinya berbeda setiap eksekusi dan sengaja tidak dicetak. - Baris 45–52: jendela perekaman dibuka dan ditutup tepat mengelilingi kedua beban kerja — hanya waktu di antara keduanya yang dihitung.
- Baris 54–56: hasil kedua fungsi dicetak sebagai bukti beban kerja benar-benar dieksekusi, lalu konfirmasi profil ditulis.
Membandingkan Kontribusi Fungsi #
Setelah program selesai, profilnya siap dibaca. Ganti nama berkas sesuai hasil di mesin Anda, lalu jalankan:
go tool pprof -top /tmp/cpu-XXXX.profOutput yang Dihasilkan:
File: contoh
Build ID: 2817bbb5357bf084c9f1d00c639cbf7d30680ba4
Type: cpu
Duration: 595.15ms, Total samples = 600ms (100.81%)
Showing nodes accounting for 600ms, 100% of 600ms total
flat flat% sum% cum cum%
340ms 56.67% 56.67% 340ms 56.67% main.hitungPrima (inline)
260ms 43.33% 100% 260ms 43.33% main.jumlahKuadratMod (inline)
0 0% 100% 600ms 100% main.main
0 0% 100% 600ms 100% runtime.mainTabel ini langsung menjawab pertanyaan pertama setiap optimasi: hitungPrima menyerap 56,67% sampel — hampir 1,3 kali lipat dari jumlahKuadratMod meski batas primanya “hanya” 1,5 juta, karena tiap kandidat prima diuji pembaginya satu per satu. Catatan (inline) menandakan compiler menempelkan fungsi ke pemanggilnya sehingga tidak ada frame terpisah — hal biasa untuk fungsi kecil. Nilai Total samples = 600ms lebih besar dari Duration: 595.15ms karena profil multi-core bisa mencatat lebih dari satu sampel per satuan waktu; itu bukan kesalahan.
Menajamkan ke Baris Kode dengan -list #
Ketika tersangka utama sudah ditentukan, pprof -list <fungsi> menumpahkan sampel ke source code baris demi baris:
go tool pprof -list hitungPrima /tmp/cpu-XXXX.profOutput yang Dihasilkan:
Total: 600ms
ROUTINE ======================== main.hitungPrima in /home/z/my-project/content/go/debugging-dan-profiling/cpu-profiling/contoh.go
340ms 340ms (flat, cum) 56.67% of Total
. . 21:func hitungPrima(batas int) int {
. . 22: jumlah := 0
50ms 50ms 23: for n := 2; n < batas; n++ {
. . 24: prima := true
40ms 40ms 25: for d := 2; d*d <= n; d++ {
250ms 250ms 26: if n%d == 0 {
. . 27: prima = false
. . 28: break
. . 29: }
. . 30: }
. . 31: if prima {Baris 26 — operasi modulo n%d == 0 — sendirian menyerap 250 ms dari 340 ms milik hitungPrima. Inilah petunjuk konkret: mengganti uji pembagian dengan saringan Eratosthenes akan menghapus operasi modulo yang sama persis. Tanda . berarti baris itu tidak pernah tercatat pada sampel mana pun.
Waktu CPU hanyalah separuh cerita; profil berikutnya menyoroti memori — berapa byte yang dialokasikan dan dari stack mana — pada chapter memory profiling.
Referensi #
- Profiling Go Programs — artikel klasik tim Go tentang alur kerja profiling dan optimasi.
- Package runtime/pprof — dokumentasi resmi API perekaman profil.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"fmt"
"os"
"runtime/pprof"
)
// jumlahKuadratMod menjumlahkan (i*i)%101 untuk i dari 0 sampai n-1.
func jumlahKuadratMod(n int) int64 {
var total int64
for i := range n {
x := int64(i)
total += (x * x) % 101
}
return total
}
// hitungPrima menghitung banyaknya bilangan prima di bawah batas
// dengan uji pembagian terhadap setiap kandidat.
func hitungPrima(batas int) int {
jumlah := 0
for n := 2; n < batas; n++ {
prima := true
for d := 2; d*d <= n; d++ {
if n%d == 0 {
prima = false
break
}
}
if prima {
jumlah++
}
}
return jumlah
}
func main() {
berkas, err := os.CreateTemp("", "cpu-*.prof")
if err != nil {
fmt.Println("gagal membuat berkas profil:", err)
return
}
if err := pprof.StartCPUProfile(berkas); err != nil {
fmt.Println("gagal memulai profil CPU:", err)
return
}
hasilKuadrat := jumlahKuadratMod(300_000_000)
hasilPrima := hitungPrima(1_500_000)
pprof.StopCPUProfile()
berkas.Close()
fmt.Println("jumlah kuadrat mod:", hasilKuadrat)
fmt.Println("prima di bawah 1500000:", hasilPrima)
fmt.Println("profil CPU ditulis: true")
}