Advanced Benchmarking
Explore sophisticated benchmarking techniques, custom metrics, and enterprise-grade performance testing strategies for production Go applications.
Custom Benchmark Metrics
Beyond the standard ns/op, B/op, and allocs/op metrics, you can implement custom measurements to capture domain-specific performance characteristics.
Custom Metrics Implementation
type BenchmarkMetrics struct {
Operations int64
TotalDuration time.Duration
Throughput float64 // ops/sec
Latency LatencyStats
Resources ResourceUsage
}
type LatencyStats struct {
Min, Max, Mean time.Duration
P50, P90, P95, P99 time.Duration
Samples []time.Duration
}
type ResourceUsage struct {
CPUTime time.Duration
MemoryPeak int64
GCPause time.Duration
Goroutines int
}
func BenchmarkWithCustomMetrics(b *testing.B) {
metrics := &BenchmarkMetrics{
Latency: LatencyStats{
Samples: make([]time.Duration, 0, b.N),
},
}
// Capture initial state
var memStats1, memStats2 runtime.MemStats
runtime.ReadMemStats(&memStats1)
startGoroutines := runtime.NumGoroutine()
b.ResetTimer()
start := time.Now()
for i := 0; i < b.N; i++ {
opStart := time.Now()
result := complexOperation()
opDuration := time.Since(opStart)
metrics.Latency.Samples = append(metrics.Latency.Samples, opDuration)
_ = result
}
metrics.TotalDuration = time.Since(start)
metrics.Operations = int64(b.N)
metrics.Throughput = float64(b.N) / metrics.TotalDuration.Seconds()
// Capture final state
runtime.ReadMemStats(&memStats2)
metrics.Resources.MemoryPeak = int64(memStats2.Sys - memStats1.Sys)
metrics.Resources.GCPause = time.Duration(memStats2.PauseTotalNs - memStats1.PauseTotalNs)
metrics.Resources.Goroutines = runtime.NumGoroutine() - startGoroutines
// Calculate latency percentiles
calculateLatencyPercentiles(metrics)
// Report custom metrics
b.ReportMetric(metrics.Throughput, "ops/sec")
b.ReportMetric(float64(metrics.Latency.P95.Nanoseconds()), "p95-ns")
b.ReportMetric(float64(metrics.Resources.MemoryPeak), "peak-bytes")
b.Logf("Custom Metrics Report:")
b.Logf(" Throughput: %.2f ops/sec", metrics.Throughput)
b.Logf(" Latency P95: %v", metrics.Latency.P95)
b.Logf(" Memory Peak: %d bytes", metrics.Resources.MemoryPeak)
b.Logf(" GC Pause: %v", metrics.Resources.GCPause)
}
Network-Aware Benchmarks
func BenchmarkNetworkLatency(b *testing.B) {
// Simulate different network conditions
networkConditions := []struct {
name string
latency time.Duration
jitter time.Duration
}{
{"LAN", 1 * time.Millisecond, 100 * time.Microsecond},
{"WAN", 50 * time.Millisecond, 10 * time.Millisecond},
{"Mobile", 200 * time.Millisecond, 50 * time.Millisecond},
}
for _, condition := range networkConditions {
b.Run(condition.name, func(b *testing.B) {
simulator := NewNetworkSimulator(condition.latency, condition.jitter)
defer simulator.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := networkAwareOperation(simulator)
_ = result
}
})
}
}
type NetworkSimulator struct {
baseLatency time.Duration
jitter time.Duration
rand *rand.Rand
}
func NewNetworkSimulator(latency, jitter time.Duration) *NetworkSimulator {
return &NetworkSimulator{
baseLatency: latency,
jitter: jitter,
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
func (ns *NetworkSimulator) SimulateDelay() {
jitterAmount := time.Duration(ns.rand.Float64() * float64(ns.jitter))
totalDelay := ns.baseLatency + jitterAmount
time.Sleep(totalDelay)
}
Database Performance Benchmarks
func BenchmarkDatabaseOperations(b *testing.B) {
db := setupTestDatabase()
defer db.Close()
// Test different connection pool sizes
poolSizes := []int{1, 5, 10, 25, 50}
for _, poolSize := range poolSizes {
db.SetMaxOpenConns(poolSize)
db.SetMaxIdleConns(poolSize / 2)
b.Run(fmt.Sprintf("Pool_%d", poolSize), func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
// Simulate realistic database workload
tx, err := db.Begin()
if err != nil {
b.Fatal(err)
}
// Read operation
var count int
err = tx.QueryRow("SELECT COUNT(*) FROM users WHERE active = true").Scan(&count)
if err != nil {
tx.Rollback()
b.Fatal(err)
}
// Write operation
_, err = tx.Exec("UPDATE users SET last_seen = NOW() WHERE id = ?",
rand.Intn(10000))
if err != nil {
tx.Rollback()
b.Fatal(err)
}
if err = tx.Commit(); err != nil {
b.Fatal(err)
}
}
})
})
}
}
Cache Performance Analysis
func BenchmarkCacheEfficiency(b *testing.B) {
cacheConfigs := []struct {
name string
size int
strategy string
}{
{"LRU_1K", 1000, "LRU"},
{"LRU_10K", 10000, "LRU"},
{"LFU_1K", 1000, "LFU"},
{"Random_1K", 1000, "Random"},
}
for _, config := range cacheConfigs {
b.Run(config.name, func(b *testing.B) {
cache := NewCache(config.size, config.strategy)
// Warm up cache
for i := 0; i < config.size/2; i++ {
cache.Set(fmt.Sprintf("key_%d", i), i)
}
hitCount := int64(0)
missCount := int64(0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := fmt.Sprintf("key_%d", rand.Intn(config.size*2))
if _, found := cache.Get(key); found {
atomic.AddInt64(&hitCount, 1)
} else {
atomic.AddInt64(&missCount, 1)
cache.Set(key, i)
}
}
hitRatio := float64(hitCount) / float64(hitCount+missCount) * 100
b.ReportMetric(hitRatio, "hit-ratio-%")
b.Logf("Cache hit ratio: %.2f%%", hitRatio)
})
}
}
Load Testing Integration
HTTP Server Load Testing
func BenchmarkHTTPServerLoad(b *testing.B) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Simulate varying response times
processingTime := time.Duration(rand.Intn(100)) * time.Millisecond
time.Sleep(processingTime)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"timestamp": time.Now(),
"processed": true,
})
})
server := httptest.NewServer(handler)
defer server.Close()
// Test different concurrency levels
concurrencyLevels := []int{1, 10, 50, 100, 200}
for _, concurrency := range concurrencyLevels {
b.Run(fmt.Sprintf("Concurrency_%d", concurrency), func(b *testing.B) {
client := &http.Client{
Transport: &http.Transport{
MaxIdleConnsPerHost: concurrency,
IdleConnTimeout: 30 * time.Second,
},
Timeout: 30 * time.Second,
}
// Track response times
var responseTimes []time.Duration
var mutex sync.Mutex
b.SetParallelism(concurrency)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
start := time.Now()
resp, err := client.Get(server.URL)
if err != nil {
b.Error(err)
continue
}
responseTime := time.Since(start)
mutex.Lock()
responseTimes = append(responseTimes, responseTime)
mutex.Unlock()
resp.Body.Close()
}
})
// Calculate and report response time percentiles
sort.Slice(responseTimes, func(i, j int) bool {
return responseTimes[i] < responseTimes[j]
})
if len(responseTimes) > 0 {
p50 := responseTimes[len(responseTimes)*50/100]
p95 := responseTimes[len(responseTimes)*95/100]
p99 := responseTimes[len(responseTimes)*99/100]
b.ReportMetric(float64(p50.Nanoseconds()), "p50-ns")
b.ReportMetric(float64(p95.Nanoseconds()), "p95-ns")
b.ReportMetric(float64(p99.Nanoseconds()), "p99-ns")
}
})
}
}
Message Queue Benchmarks
func BenchmarkMessageQueue(b *testing.B) {
queueSizes := []int{100, 1000, 10000}
for _, queueSize := range queueSizes {
b.Run(fmt.Sprintf("QueueSize_%d", queueSize), func(b *testing.B) {
queue := make(chan Message, queueSize)
// Start consumers
numConsumers := runtime.NumCPU()
var wg sync.WaitGroup
for i := 0; i < numConsumers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for msg := range queue {
processMessage(msg)
}
}()
}
b.ResetTimer()
// Producer benchmark
for i := 0; i < b.N; i++ {
msg := Message{
ID: i,
Payload: fmt.Sprintf("message_%d", i),
Time: time.Now(),
}
select {
case queue <- msg:
default:
b.Error("Queue full")
}
}
close(queue)
wg.Wait()
})
}
}
Microbenchmark Suites
Algorithm Comparison Framework
type AlgorithmBenchmark struct {
Name string
Algorithm func([]int) int
Setup func() []int
}
func BenchmarkAlgorithmSuite(b *testing.B) {
algorithms := []AlgorithmBenchmark{
{
Name: "BubbleSort",
Algorithm: bubbleSort,
Setup: func() []int { return generateRandomSlice(1000) },
},
{
Name: "QuickSort",
Algorithm: quickSort,
Setup: func() []int { return generateRandomSlice(1000) },
},
{
Name: "MergeSort",
Algorithm: mergeSort,
Setup: func() []int { return generateRandomSlice(1000) },
},
}
inputSizes := []int{100, 1000, 10000}
for _, size := range inputSizes {
for _, algo := range algorithms {
b.Run(fmt.Sprintf("%s_Size_%d", algo.Name, size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
data := generateRandomSlice(size)
b.StartTimer()
result := algo.Algorithm(data)
_ = result
}
})
}
}
}
Data Structure Performance
func BenchmarkDataStructures(b *testing.B) {
operations := []string{"Insert", "Lookup", "Delete"}
sizes := []int{1000, 10000, 100000}
for _, size := range sizes {
for _, op := range operations {
// Map benchmark
b.Run(fmt.Sprintf("Map_%s_Size_%d", op, size), func(b *testing.B) {
m := make(map[int]bool)
// Pre-populate for lookup/delete tests
if op != "Insert" {
for i := 0; i < size; i++ {
m[i] = true
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := rand.Intn(size)
switch op {
case "Insert":
m[key] = true
case "Lookup":
_ = m[key]
case "Delete":
delete(m, key)
}
}
})
// Slice benchmark (for comparison)
b.Run(fmt.Sprintf("Slice_%s_Size_%d", op, size), func(b *testing.B) {
slice := make([]int, 0, size)
// Pre-populate for lookup/delete tests
if op != "Insert" {
for i := 0; i < size; i++ {
slice = append(slice, i)
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
value := rand.Intn(size)
switch op {
case "Insert":
slice = append(slice, value)
case "Lookup":
for _, v := range slice {
if v == value {
break
}
}
case "Delete":
for i, v := range slice {
if v == value {
slice = append(slice[:i], slice[i+1:]...)
break
}
}
}
}
})
}
}
}
Performance Regression Detection
Automated Threshold Checking
type PerformanceThreshold struct {
MaxLatency time.Duration
MaxMemoryUsage int64
MinThroughput float64
MaxAllocations int64
}
func BenchmarkWithThresholds(b *testing.B) {
thresholds := PerformanceThreshold{
MaxLatency: 10 * time.Millisecond,
MaxMemoryUsage: 1024 * 1024, // 1MB
MinThroughput: 1000, // ops/sec
MaxAllocations: 10,
}
var totalLatency time.Duration
var maxMemory int64
var totalAllocs int64
b.ReportAllocs()
for i := 0; i < b.N; i++ {
start := time.Now()
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
result := functionUnderTest()
runtime.ReadMemStats(&m2)
latency := time.Since(start)
memUsed := int64(m2.Alloc - m1.Alloc)
allocs := int64(m2.Mallocs - m1.Mallocs)
totalLatency += latency
if memUsed > maxMemory {
maxMemory = memUsed
}
totalAllocs += allocs
_ = result
}
avgLatency := totalLatency / time.Duration(b.N)
throughput := float64(b.N) / totalLatency.Seconds()
avgAllocs := totalAllocs / int64(b.N)
// Check thresholds
if avgLatency > thresholds.MaxLatency {
b.Errorf("Latency threshold exceeded: %v > %v", avgLatency, thresholds.MaxLatency)
}
if maxMemory > thresholds.MaxMemoryUsage {
b.Errorf("Memory threshold exceeded: %d > %d bytes", maxMemory, thresholds.MaxMemoryUsage)
}
if throughput < thresholds.MinThroughput {
b.Errorf("Throughput threshold not met: %.2f < %.2f ops/sec", throughput, thresholds.MinThroughput)
}
if avgAllocs > thresholds.MaxAllocations {
b.Errorf("Allocation threshold exceeded: %d > %d allocs/op", avgAllocs, thresholds.MaxAllocations)
}
b.ReportMetric(float64(avgLatency.Nanoseconds()), "avg-latency-ns")
b.ReportMetric(throughput, "throughput-ops/sec")
b.ReportMetric(float64(maxMemory), "peak-memory-bytes")
}
Continuous Performance Monitoring
func BenchmarkContinuousMonitoring(b *testing.B) {
// Performance baseline from previous runs
baseline := PerformanceBaseline{
LatencyP95: 5 * time.Millisecond,
Throughput: 2000,
MemoryPeak: 512 * 1024,
}
// Allow 10% degradation
tolerance := 0.10
metrics := measurePerformance(b)
// Compare against baseline
latencyDelta := (metrics.LatencyP95.Seconds() - baseline.LatencyP95.Seconds()) / baseline.LatencyP95.Seconds()
throughputDelta := (baseline.Throughput - metrics.Throughput) / baseline.Throughput
memoryDelta := (float64(metrics.MemoryPeak - baseline.MemoryPeak)) / float64(baseline.MemoryPeak)
if latencyDelta > tolerance {
b.Errorf("Latency regression: %.2f%% increase", latencyDelta*100)
}
if throughputDelta > tolerance {
b.Errorf("Throughput regression: %.2f%% decrease", throughputDelta*100)
}
if memoryDelta > tolerance {
b.Errorf("Memory regression: %.2f%% increase", memoryDelta*100)
}
// Update baseline if performance improved
if latencyDelta < -0.05 && throughputDelta < -0.05 {
updatePerformanceBaseline(metrics)
b.Logf("Performance baseline updated")
}
}
Advanced benchmarking transforms performance testing from a simple measurement tool into a comprehensive performance engineering platform, enabling sophisticated analysis, regression detection, and continuous performance optimization in production Go applications.