Unit test menjawab apakah kode berperilaku benar, tetapi tidak menjawab pertanyaan lain yang sama pentingnya: seberapa cepat kode tersebut berjalan? Ketika dua implementasi menghasilkan keluaran sama, pilihan sebaiknya didasarkan pada pengukuran, bukan dugaan. Go menyediakan benchmark bawaan melalui tipe testing.B dan flag -bench milik go test, tanpa library pihak ketiga. Pada chapter ini kita akan mengukur dua cara menggabungkan potongan string, membaca kolom hasil pengukuran, lalu mengenali flag benchmark yang paling sering dipakai.
Fungsi Benchmark dan testing.B #
Benchmark ditulis sebagai fungsi bernama BenchmarkXxx pada berkas _test.go, dengan satu parameter bertipe *testing.B. Tipe testing.B mewarisi kemampuan testing.T seperti Errorf dan Fatalf, ditambah penghitung iterasi yang mengatur berapa kali kode terukur diulang. Sejak Go 1.24 bentuk idiomatiknya memakai for b.Loop(): framework menentukan jumlah iterasi dan mengukur total waktu di dalam loop tersebut.
Kode di luar for b.Loop() dihitung sebagai setup dan tidak ikut diukur, sehingga penyiapan data sebaiknya diletakkan sebelum loop. Bentuk lama sebelum Go 1.24 memakai field b.N secara manual:
for i := 0; i < b.N; i++ {
GabungDenganPlus(kataBench)
}Perintah go test biasa tidak menjalankan benchmark; flag -bench mengaktifkannya dengan pola nama fungsi.
Kode yang Akan Diukur #
Kita membandingkan dua fungsi yang tugasnya sama: menggabungkan potongan string menjadi satu. GabungDenganPlus memakai operator += yang membuat string baru pada setiap penyambungan, sedangkan GabungDenganBuilder memakai strings.Builder yang menampung data dalam satu buffer. Simpan kode berikut sebagai penggabung.go:
package main
import (
"fmt"
"strings"
)
// kataBench adalah data tetap yang dipakai benchmark agar kedua fungsi
// diukur dengan input yang sama dan hasilnya dapat dibandingkan.
var kataBench = []string{"belajar ", "pengujian ", "di ", "bahasa ", "go"}
// GabungDenganPlus menggabungkan potongan kata memakai operator += .
// Setiap penyambungan membuat string baru, sehingga alokasi memori bertambah.
func GabungDenganPlus(kata []string) string {
hasil := ""
for _, k := range kata {
hasil += k
}
return hasil
}
// GabungDenganBuilder menggabungkan potongan kata memakai strings.Builder
// yang menampung data di satu buffer sebelum dialokasikan menjadi string final.
func GabungDenganBuilder(kata []string) string {
var b strings.Builder
for _, k := range kata {
b.WriteString(k)
}
return b.String()
}
func main() {
fmt.Println(GabungDenganPlus(kataBench))
fmt.Println(GabungDenganBuilder(kataBench))
}Jalankan berkas tersebut dari folder chapter:
go run penggabung.goOutput yang Dihasilkan:
belajar pengujian di bahasa go
belajar pengujian di bahasa goMari kita telaah baris demi baris kode di atas:
- Baris 10 (
var kataBench = ...): mendefinisikan data uji tetap di level package, dipakai bersama oleh benchmark dan unit test. - Baris 14-19 (
func GabungDenganPlus): menggabungkan string dengan operator+=. Setiap iterasi mengalokasikan string baru karena string di Go bersifat tidak dapat diubah (immutable). - Baris 24-29 (
func GabungDenganBuilder): memakaistrings.Builderyang menyimpan data di buffer internal, lalu memanggilb.String()sekali di akhir untuk menghasilkan string final. - Baris 32-34 (
func main): mencetak hasil kedua fungsi sebagai bukti keduanya identik, sehingga pembeda tinggal performa.
Menulis Benchmark #
Berkas penggabung_test.go berisi dua benchmark dan satu unit test yang memastikan kedua fungsi setara:
package main
import "testing"
func TestGabung(t *testing.T) {
naif := GabungDenganPlus(kataBench)
builder := GabungDenganBuilder(kataBench)
if naif != builder {
t.Errorf("hasil kedua fungsi berbeda: %q vs %q", naif, builder)
}
if naif != "belajar pengujian di bahasa go" {
t.Errorf("hasil tidak sesuai harapan: %q", naif)
}
}
func BenchmarkGabungDenganPlus(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
GabungDenganPlus(kataBench)
}
}
func BenchmarkGabungDenganBuilder(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
GabungDenganBuilder(kataBench)
}
}Poin penting pada berkas tersebut:
- Baris 5-14 (
func TestGabung): unit test biasa yang memastikan kedua fungsi menghasilkan keluaran sama. Benchmark tanpa kebenaran hasil tidak berarti, karena implementasi tercepat yang salah tetap salah. - Baris 16 (
func BenchmarkGabungDenganPlus(b *testing.B)): nama fungsi benchmark wajib diawaliBenchmarkagar ditemukan oleh flag-bench. - Baris 17 (
b.ReportAllocs()): meminta framework melaporkan jumlah byte dan alokasi memori per iterasi, bukan hanya waktu. - Baris 18-20 (
for b.Loop()): hanya panggilan fungsi di dalam loop ini yang diukur; jumlah iterasi ditentukan oleh framework.
Menjalankan Benchmark #
Jalankan benchmark dengan pola . agar semua fungsi Benchmark ikut:
go test -bench=. -benchtime=10xOutput yang Dihasilkan:
goos: linux
goarch: amd64
pkg: tutorial-website/content/go/testing/benchmark
cpu: Intel(R) Xeon(R) Processor
BenchmarkGabungDenganPlus-2 10 344.2 ns/op 112 B/op 4 allocs/op
BenchmarkGabungDenganBuilder-2 10 310.0 ns/op 80 B/op 3 allocs/op
PASS
ok tutorial-website/content/go/testing/benchmark 0.003sFlag -benchtime=10x membatasi setiap benchmark menjadi tepat 10 iterasi sehingga perintah selesai hampir seketika; angka waktunya menjadi sangat kasar. Tanpa flag ini, go test -bench memakai -benchtime=1s bawaan: framework menaikkan jumlah iterasi bertahap sampai total waktu sekitar satu detik, lalu membagi total waktu dengan jumlah iterasi untuk mendapat ns/op yang jauh lebih stabil.
Membaca Hasil Benchmark #
Setiap baris hasil terdiri atas kolom-kolom berikut:
| Kolom | Arti |
|---|---|
BenchmarkGabungDenganPlus-2 | Nama fungsi; angka 2 adalah nilai GOMAXPROCS saat pengukuran |
10 | Jumlah iterasi yang dijalankan (b.N) |
344.2 ns/op | Rata-rata waktu per iterasi dalam nanodetik |
112 B/op | Byte memori yang dialokasikan per iterasi |
4 allocs/op | Jumlah alokasi memori per iterasi |
Pada hasil di atas, GabungDenganPlus melakukan 4 alokasi sedangkan GabungDenganBuilder hanya 3. Dengan lima potongan kata selisihnya memang tipis, tetapi operator += menyalin seluruh isi string lama pada setiap penyambungan, sehingga biayanya membesar cepat pada data panjang. Perbesar kataBench menjadi ratusan potongan lalu ukur ulang untuk melihat jurangnya.
Flag Benchmark yang Sering Dipakai #
| Flag | Kegunaan |
|---|---|
-bench=. | Pola nama benchmark yang dijalankan (. berarti semua) |
-benchtime=10x | Batas iterasi tetap; ganti dengan 1s atau 100ms untuk batas waktu |
-benchmem | Tampilkan B/op dan allocs/op untuk semua benchmark sekaligus |
-count=5 | Ulangi seluruh benchmark 5 kali untuk melihat variabilitas |
-cpu=1,2,4 | Ulangi benchmark dengan beberapa nilai GOMAXPROCS |
-run=^$ | Lewati semua unit test agar hanya benchmark yang berjalan |
Dengan menguasai pola BenchmarkXxx, b.Loop(), dan b.ReportAllocs(), kita kini punya alat untuk membandingkan implementasi berdasarkan data. Selanjutnya kita akan bertanya arah sebaliknya: bagaimana menemukan input yang membuat fungsi gagal, tanpa harus menebak-nebak sendiri — melalui fuzz testing.
Referensi #
- Package testing — Benchmarks — aturan penulisan fungsi benchmark.
- Tipe testing.B
— method
BtermasukLoopdanReportAllocs. - Testing flags pada perintah go
— seluruh flag
go testseperti-benchtimedan-count.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
penggabung_test.go
Unduh berkaspackage main
import "testing"
func TestGabung(t *testing.T) {
naif := GabungDenganPlus(kataBench)
builder := GabungDenganBuilder(kataBench)
if naif != builder {
t.Errorf("hasil kedua fungsi berbeda: %q vs %q", naif, builder)
}
if naif != "belajar pengujian di bahasa go" {
t.Errorf("hasil tidak sesuai harapan: %q", naif)
}
}
func BenchmarkGabungDenganPlus(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
GabungDenganPlus(kataBench)
}
}
func BenchmarkGabungDenganBuilder(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
GabungDenganBuilder(kataBench)
}
}
penggabung.go
Unduh berkaspackage main
import (
"fmt"
"strings"
)
// kataBench adalah data tetap yang dipakai benchmark agar kedua fungsi
// diukur dengan input yang sama dan hasilnya dapat dibandingkan.
var kataBench = []string{"belajar ", "pengujian ", "di ", "bahasa ", "go"}
// GabungDenganPlus menggabungkan potongan kata memakai operator += .
// Setiap penyambungan membuat string baru, sehingga alokasi memori bertambah.
func GabungDenganPlus(kata []string) string {
hasil := ""
for _, k := range kata {
hasil += k
}
return hasil
}
// GabungDenganBuilder menggabungkan potongan kata memakai strings.Builder
// yang menampung data di satu buffer sebelum dialokasikan menjadi string final.
func GabungDenganBuilder(kata []string) string {
var b strings.Builder
for _, k := range kata {
b.WriteString(k)
}
return b.String()
}
func main() {
fmt.Println(GabungDenganPlus(kataBench))
fmt.Println(GabungDenganBuilder(kataBench))
}