Minimizing GC Pressure
Strategies to reduce garbage collection overhead:
package main
import (
"fmt"
"runtime"
"time"
)
// Struct with pointer fields - creates GC pressure
type HighPressure struct {
name *string
value *int
enabled *bool
data *[]byte
}
// Struct with value fields - reduces GC pressure
type LowPressure struct {
name string
value int
enabled bool
data []byte // Still contains a pointer, but fewer overall
}
func createHighPressureObjects(count int) []*HighPressure {
result := make([]*HighPressure, count)
for i := 0; i < count; i++ {
name := fmt.Sprintf("object-%d", i)
value := i
enabled := i%2 == 0
data := make([]byte, 100)
result[i] = &HighPressure{
name: &name,
value: &value,
enabled: &enabled,
data: &data,
}
}
return result
}
func createLowPressureObjects(count int) []*LowPressure {
result := make([]*LowPressure, count)
for i := 0; i < count; i++ {
result[i] = &LowPressure{
name: fmt.Sprintf("object-%d", i),
value: i,
enabled: i%2 == 0,
data: make([]byte, 100),
}
}
return result
}
func measureGCStats(label string, f func()) {
// Clear previous garbage
runtime.GC()
// Get initial stats
var statsBefore runtime.MemStats
runtime.ReadMemStats(&statsBefore)
startTime := time.Now()
// Run the test function
f()
// Force final GC to get accurate measurements
runtime.GC()
duration := time.Since(startTime)
// Get final stats
var statsAfter runtime.MemStats
runtime.ReadMemStats(&statsAfter)
fmt.Printf("--- %s ---\n", label)
fmt.Printf("Duration: %v\n", duration)
fmt.Printf("GC runs: %d\n", statsAfter.NumGC-statsBefore.NumGC)
fmt.Printf("GC time: %v\n", time.Duration(statsAfter.PauseTotalNs-statsBefore.PauseTotalNs))
fmt.Printf("Heap allocations: %d KB\n", (statsAfter.TotalAlloc-statsBefore.TotalAlloc)/1024)
fmt.Printf("Objects allocated: %d\n\n", statsAfter.Mallocs-statsBefore.Mallocs)
}
func main() {
const objectCount = 100000
// Measure high-pressure objects
measureGCStats("High GC Pressure", func() {
objects := createHighPressureObjects(objectCount)
_ = objects // Prevent compiler optimization
})
// Measure low-pressure objects
measureGCStats("Low GC Pressure", func() {
objects := createLowPressureObjects(objectCount)
_ = objects // Prevent compiler optimization
})
}
Write Barrier Optimization
Understanding write barriers can help optimize performance in GC-heavy applications:
package main
import (
"fmt"
"runtime"
"time"
)
// Structure with many pointers - more write barriers
type ManyPointers struct {
a, b, c, d, e *int
f, g, h, i, j *string
}
// Structure with fewer pointers - fewer write barriers
type FewerPointers struct {
a, b, c, d, e int
f, g, h, i, j string
}
func modifyManyPointers(obj *ManyPointers, iterations int) {
for i := 0; i < iterations; i++ {
// Each of these assignments triggers a write barrier during GC
val := i
obj.a = &val
obj.b = &val
obj.c = &val
obj.d = &val
obj.e = &val
str := fmt.Sprintf("iteration-%d", i)
obj.f = &str
obj.g = &str
obj.h = &str
obj.i = &str
obj.j = &str
}
}
func modifyFewerPointers(obj *FewerPointers, iterations int) {
for i := 0; i < iterations; i++ {
// These assignments don't trigger write barriers
obj.a = i
obj.b = i
obj.c = i
obj.d = i
obj.e = i
obj.f = fmt.Sprintf("iteration-%d", i)
obj.g = fmt.Sprintf("iteration-%d", i)
obj.h = fmt.Sprintf("iteration-%d", i)
obj.i = fmt.Sprintf("iteration-%d", i)
obj.j = fmt.Sprintf("iteration-%d", i)
}
}
func main() {
const iterations = 1000000
// Test with many pointers (more write barriers)
manyPtrObj := &ManyPointers{}
start := time.Now()
modifyManyPointers(manyPtrObj, iterations)
manyPtrDuration := time.Since(start)
// Test with fewer pointers (fewer write barriers)
fewerPtrObj := &FewerPointers{}
start = time.Now()
modifyFewerPointers(fewerPtrObj, iterations)
fewerPtrDuration := time.Since(start)
// Print results
fmt.Printf("Many pointers (more write barriers): %v\n", manyPtrDuration)
fmt.Printf("Fewer pointers (fewer write barriers): %v\n", fewerPtrDuration)
fmt.Printf("Performance difference: %.2fx\n",
float64(manyPtrDuration)/float64(fewerPtrDuration))
// Print GC statistics
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
fmt.Printf("\nGC statistics:\n")
fmt.Printf("GC cycles: %d\n", stats.NumGC)
fmt.Printf("Total GC pause: %v\n", time.Duration(stats.PauseTotalNs))
fmt.Printf("GC CPU fraction: %.2f%%\n", stats.GCCPUFraction*100)
}
Memory Profiling and Debugging
Identifying and resolving memory issues requires sophisticated profiling and debugging techniques.