ข้ามไปยังเนื้อหา

Benchmarks

benchmark วัดว่าโค้ดใช้เวลานานเท่าไร Go benchmarks อยู่ในไฟล์ _test.go เคียงข้าง unit tests และรันด้วยคำสั่ง go test เดียวกัน — เพียงแต่ใช้ flags ต่างกัน

ฟังก์ชัน benchmark มี signature ดังนี้:

func BenchmarkXxx(b *testing.B) {
// ... setup code (ไม่ถูกวัด) ...
for b.Loop() { // Go 1.24+ รูปแบบที่แนะนำ
// โค้ดที่ต้องการวัด
}
}

ค่า testing.B ขับเคลื่อน loop framework จะเริ่มด้วยค่า b.N ขนาดเล็กและเพิ่มขึ้นเรื่อยๆ จนกว่า benchmark จะรันนานพอที่จะให้การวัดที่เสถียร คุณไม่ต้องกำหนด b.N เอง

Go 1.24 แนะนำ b.Loop() เป็นทางเลือกที่ดีกว่า classic b.N counter loop:

// รูปแบบ classic — ใช้ได้กับ Go ทุกเวอร์ชัน
func BenchmarkFibClassic(b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(20)
}
}
// รูปแบบ modern — Go 1.24+
func BenchmarkFib(b *testing.B) {
for b.Loop() {
Fibonacci(20)
}
}

ทั้งสองรูปแบบถูกต้อง b.Loop() แนะนำใน Go 1.24+ เพราะจัดการ timer reset ภายในเองและมีโอกาสผิดพลาดน้อยกว่า

หาก benchmark ต้องการ setup ที่ซับซ้อน (เปิดไฟล์, สร้าง slice ขนาดใหญ่) ให้ทำก่อน loop และเรียก b.ResetTimer() เพื่อไม่ให้เวลา setup ถูกนับ:

func BenchmarkIsPrimeList(b *testing.B) {
numbers := make([]int, 1000)
for i := range numbers {
numbers[i] = i + 2
}
b.ResetTimer() // เริ่มวัดจากจุดนี้
for b.Loop() {
for _, n := range numbers {
IsPrime(n)
}
}
}

Unit tests ไม่ รัน benchmarks โดยค่าเริ่มต้น ใช้ flag -bench:

Terminal window
# รัน benchmarks ทั้งหมด แสดง memory allocations
go test -bench=. -benchmem ./...
# รันเฉพาะ benchmarks ที่ชื่อตรงกับ pattern
go test -bench=BenchmarkFib -benchmem ./...
# รัน benchmarks อย่างน้อย 5 วินาทีต่อฟังก์ชัน
go test -bench=. -benchtime=5s ./...
BenchmarkFib-8 5000000 234 ns/op
BenchmarkFibClassic-8 5000000 236 ns/op 0 B/op 0 allocs/op
คอลัมน์ความหมาย
suffix -8ค่า GOMAXPROCS (8 logical CPUs)
5000000จำนวน iterations ที่ b.N รัน
234 ns/opnanoseconds ต่อ operation
0 B/opbytes ที่ allocate ต่อ operation (ต้องใช้ -benchmem)
0 allocs/opheap allocations ต่อ operation (ต้องใช้ -benchmem)

Fibonacci ไม่ allocate อะไรเลยเพราะใช้แค่ stack variables — นั่นคือเหตุผลที่ทั้งสองคอลัมน์ alloc เป็นศูนย์ ฟังก์ชันที่สร้าง []string จะแสดงค่าที่ไม่ใช่ศูนย์

สิ่งที่ได้ประโยชน์ต้นทุน
go test -benchวัด performance ด้วยเครื่องมือ built-inต้องรันซ้ำหลายครั้ง ระวัง noise จาก GC และ CPU throttling
b.N auto-calibrationframework กำหนด iteration count ให้ได้ผลที่เสถียรถ้าใส่ setup ใน loop = benchmark นับ setup ด้วย
b.ReportAllocs()เห็น heap allocation ต่อ operation ชัดเจนต้องเข้าใจ Escape Analysis เพื่อตีความผลลัพธ์
pprof profilingวิเคราะห์ CPU/memory hotspot เชิงลึกoverhead ขณะ profiling — อย่าใช้ใน production โดยไม่ตั้งใจ
  • benchmark result บน dev machine = production — ต้องคำนึง CPU throttling, GC state, background processes ที่ต่างกัน
  • b.N คือจำนวนที่กำหนดเอง — Go calibrate b.N อัตโนมัติให้ benchmark ใช้เวลาประมาณ 1 วินาที — อย่ากำหนดค่า b.N เอง
  • allocs/op = 0 หมายถึง optimal เสมอ — zero allocation ดี แต่ถ้า CPU work เพิ่มขึ้นแทน อาจไม่ได้ดีกว่า
  • benchmark สำหรับ micro-optimization เท่านั้น — benchmark ช่วย validate architectural decision ได้ด้วย เช่น channel vs mutex, slice vs map

💡 ตัวอย่างจากของจริง

Kubernetes ใช้ benchmark สำหรับ JSON marshaling ของ API object และ etcd client latency — ป้องกัน performance regression ระหว่าง release

Go standard library เพิ่ม benchmark ทุก PR ที่แตะ hot path เช่น encoding/json, strings, bytes

Prometheus benchmark metric registration และ observation ใน hot path — ต้องรองรับ millions of observations per second

benchmark framework ทำอะไรกับ b.N ระหว่างการรัน benchmark?
flag ใดทำให้ go test รายงาน bytes ที่ allocate ต่อ operation?
ควรเรียก b.ResetTimer() เมื่อใดใน benchmark?
รูปแบบ benchmark loop ที่แนะนำใน Go 1.24+ คืออะไร?