Go Performance Tuning Best Practices¶
Objective: Master senior-level Go performance tuning patterns for production systems. When you need to build high-performance applications, when you want to optimize existing code, when you need enterprise-grade performance patternsโthese best practices become your weapon of choice.
Core Principles¶
- Measure First: Profile before optimizing
- Bottleneck Identification: Find the real performance bottlenecks
- Incremental Optimization: Optimize one component at a time
- Benchmark Everything: Use benchmarks to validate improvements
- Monitor in Production: Continuously monitor performance metrics
Profiling and Benchmarking¶
CPU Profiling¶
// internal/profiling/cpu_profiler.go
package profiling
import (
"context"
"fmt"
"log"
"os"
"runtime/pprof"
"time"
)
// CPUProfiler handles CPU profiling
type CPUProfiler struct {
enabled bool
file *os.File
}
// NewCPUProfiler creates a new CPU profiler
func NewCPUProfiler() *CPUProfiler {
return &CPUProfiler{enabled: false}
}
// Start starts CPU profiling
func (cp *CPUProfiler) Start(filename string) error {
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("failed to create profile file: %w", err)
}
if err := pprof.StartCPUProfile(file); err != nil {
file.Close()
return fmt.Errorf("failed to start CPU profile: %w", err)
}
cp.enabled = true
cp.file = file
log.Printf("CPU profiling started, writing to %s", filename)
return nil
}
// Stop stops CPU profiling
func (cp *CPUProfiler) Stop() error {
if !cp.enabled {
return nil
}
pprof.StopCPUProfile()
cp.enabled = false
if err := cp.file.Close(); err != nil {
return fmt.Errorf("failed to close profile file: %w", err)
}
log.Println("CPU profiling stopped")
return nil
}
// ProfileFunction profiles a function
func (cp *CPUProfiler) ProfileFunction(ctx context.Context, fn func() error) error {
if !cp.enabled {
return fn()
}
return fn()
}
Memory Profiling¶
// internal/profiling/memory_profiler.go
package profiling
import (
"fmt"
"os"
"runtime"
"runtime/pprof"
"time"
)
// MemoryProfiler handles memory profiling
type MemoryProfiler struct {
enabled bool
}
// NewMemoryProfiler creates a new memory profiler
func NewMemoryProfiler() *MemoryProfiler {
return &MemoryProfiler{enabled: false}
}
// StartMemoryProfiling starts memory profiling
func (mp *MemoryProfiler) StartMemoryProfiling() {
mp.enabled = true
runtime.MemProfileRate = 1
}
// StopMemoryProfiling stops memory profiling and writes profile
func (mp *MemoryProfiler) StopMemoryProfiling(filename string) error {
if !mp.enabled {
return nil
}
file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("failed to create memory profile file: %w", err)
}
defer file.Close()
if err := pprof.WriteHeapProfile(file); err != nil {
return fmt.Errorf("failed to write memory profile: %w", err)
}
mp.enabled = false
return nil
}
// GetMemoryStats returns current memory statistics
func (mp *MemoryProfiler) GetMemoryStats() runtime.MemStats {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return m
}
// ForceGC forces garbage collection
func (mp *MemoryProfiler) ForceGC() {
runtime.GC()
}
Benchmarking¶
// internal/benchmarking/benchmark.go
package benchmarking
import (
"fmt"
"testing"
"time"
)
// BenchmarkResult represents a benchmark result
type BenchmarkResult struct {
Name string
Duration time.Duration
Memory int64
Allocs int64
}
// BenchmarkSuite represents a benchmark suite
type BenchmarkSuite struct {
benchmarks []BenchmarkResult
}
// NewBenchmarkSuite creates a new benchmark suite
func NewBenchmarkSuite() *BenchmarkSuite {
return &BenchmarkSuite{
benchmarks: make([]BenchmarkResult, 0),
}
}
// AddBenchmark adds a benchmark to the suite
func (bs *BenchmarkSuite) AddBenchmark(name string, fn func()) {
start := time.Now()
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
fn()
runtime.ReadMemStats(&m2)
duration := time.Since(start)
bs.benchmarks = append(bs.benchmarks, BenchmarkResult{
Name: name,
Duration: duration,
Memory: int64(m2.Alloc - m1.Alloc),
Allocs: int64(m2.Mallocs - m1.Mallocs),
})
}
// RunBenchmarks runs all benchmarks
func (bs *BenchmarkSuite) RunBenchmarks() {
for _, benchmark := range bs.benchmarks {
fmt.Printf("Benchmark: %s\n", benchmark.Name)
fmt.Printf(" Duration: %v\n", benchmark.Duration)
fmt.Printf(" Memory: %d bytes\n", benchmark.Memory)
fmt.Printf(" Allocations: %d\n", benchmark.Allocs)
fmt.Println()
}
}
// BenchmarkFunction benchmarks a function
func BenchmarkFunction(b *testing.B, fn func()) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
fn()
}
}
// BenchmarkFunctionWithMemory benchmarks a function with memory tracking
func BenchmarkFunctionWithMemory(b *testing.B, fn func()) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
fn()
}
}
Memory Optimization¶
Object Pooling¶
// internal/pooling/object_pool.go
package pooling
import (
"sync"
)
// ObjectPool represents an object pool
type ObjectPool struct {
pool sync.Pool
new func() interface{}
}
// NewObjectPool creates a new object pool
func NewObjectPool(newFunc func() interface{}) *ObjectPool {
return &ObjectPool{
pool: sync.Pool{
New: newFunc,
},
new: newFunc,
}
}
// Get gets an object from the pool
func (op *ObjectPool) Get() interface{} {
return op.pool.Get()
}
// Put puts an object back into the pool
func (op *ObjectPool) Put(obj interface{}) {
op.pool.Put(obj)
}
// BufferPool represents a buffer pool
type BufferPool struct {
pool sync.Pool
}
// NewBufferPool creates a new buffer pool
func NewBufferPool() *BufferPool {
return &BufferPool{
pool: sync.Pool{
New: func() interface{} {
return make([]byte, 0, 1024)
},
},
}
}
// Get gets a buffer from the pool
func (bp *BufferPool) Get() []byte {
return bp.pool.Get().([]byte)
}
// Put puts a buffer back into the pool
func (bp *BufferPool) Put(buf []byte) {
buf = buf[:0] // Reset length
bp.pool.Put(buf)
}
String Optimization¶
// internal/optimization/string_optimization.go
package optimization
import (
"strings"
"unsafe"
)
// StringOptimizer provides string optimization utilities
type StringOptimizer struct{}
// NewStringOptimizer creates a new string optimizer
func NewStringOptimizer() *StringOptimizer {
return &StringOptimizer{}
}
// ConcatStrings concatenates strings efficiently
func (so *StringOptimizer) ConcatStrings(strs ...string) string {
if len(strs) == 0 {
return ""
}
if len(strs) == 1 {
return strs[0]
}
var builder strings.Builder
builder.Grow(so.calculateTotalLength(strs))
for _, str := range strs {
builder.WriteString(str)
}
return builder.String()
}
// calculateTotalLength calculates the total length of strings
func (so *StringOptimizer) calculateTotalLength(strs []string) int {
total := 0
for _, str := range strs {
total += len(str)
}
return total
}
// StringToBytes converts string to bytes without allocation
func (so *StringOptimizer) StringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&s))
}
// BytesToString converts bytes to string without allocation
func (so *StringOptimizer) BytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// StringBuilder represents an optimized string builder
type StringBuilder struct {
buffer []byte
length int
}
// NewStringBuilder creates a new string builder
func NewStringBuilder(initialCapacity int) *StringBuilder {
return &StringBuilder{
buffer: make([]byte, 0, initialCapacity),
length: 0,
}
}
// WriteString writes a string to the builder
func (sb *StringBuilder) WriteString(s string) {
sb.buffer = append(sb.buffer, s...)
sb.length += len(s)
}
// WriteByte writes a byte to the builder
func (sb *StringBuilder) WriteByte(b byte) {
sb.buffer = append(sb.buffer, b)
sb.length++
}
// String returns the built string
func (sb *StringBuilder) String() string {
return string(sb.buffer)
}
// Reset resets the builder
func (sb *StringBuilder) Reset() {
sb.buffer = sb.buffer[:0]
sb.length = 0
}
Slice Optimization¶
// internal/optimization/slice_optimization.go
package optimization
import (
"reflect"
"unsafe"
)
// SliceOptimizer provides slice optimization utilities
type SliceOptimizer struct{}
// NewSliceOptimizer creates a new slice optimizer
func NewSliceOptimizer() *SliceOptimizer {
return &SliceOptimizer{}
}
// PreallocateSlice preallocates a slice with known capacity
func (so *SliceOptimizer) PreallocateSlice[T any](capacity int) []T {
return make([]T, 0, capacity)
}
// ReuseSlice reuses a slice by resetting its length
func (so *SliceOptimizer) ReuseSlice[T any](slice []T) []T {
return slice[:0]
}
// FastAppend appends to a slice efficiently
func (so *SliceOptimizer) FastAppend[T any](slice []T, elements ...T) []T {
if len(elements) == 0 {
return slice
}
newLen := len(slice) + len(elements)
if newLen > cap(slice) {
newCap := cap(slice) * 2
if newCap < newLen {
newCap = newLen
}
newSlice := make([]T, len(slice), newCap)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[:newLen]
copy(slice[len(slice)-len(elements):], elements)
return slice
}
// FastCopy copies a slice efficiently
func (so *SliceOptimizer) FastCopy[T any](src []T) []T {
if len(src) == 0 {
return nil
}
dst := make([]T, len(src))
copy(dst, src)
return dst
}
// UnsafeSliceHeader returns the slice header
func (so *SliceOptimizer) UnsafeSliceHeader(slice interface{}) reflect.SliceHeader {
return *(*reflect.SliceHeader)(unsafe.Pointer(&slice))
}
Concurrency Optimization¶
Goroutine Pool¶
// internal/optimization/goroutine_pool.go
package optimization
import (
"context"
"sync"
"time"
)
// GoroutinePool represents a goroutine pool
type GoroutinePool struct {
workers int
jobQueue chan func()
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
}
// NewGoroutinePool creates a new goroutine pool
func NewGoroutinePool(workers int) *GoroutinePool {
ctx, cancel := context.WithCancel(context.Background())
return &GoroutinePool{
workers: workers,
jobQueue: make(chan func(), workers*2),
ctx: ctx,
cancel: cancel,
}
}
// Start starts the goroutine pool
func (gp *GoroutinePool) Start() {
for i := 0; i < gp.workers; i++ {
gp.wg.Add(1)
go gp.worker(i)
}
}
// worker processes jobs from the queue
func (gp *GoroutinePool) worker(id int) {
defer gp.wg.Done()
for {
select {
case job := <-gp.jobQueue:
job()
case <-gp.ctx.Done():
return
}
}
}
// Submit submits a job to the pool
func (gp *GoroutinePool) Submit(job func()) error {
select {
case gp.jobQueue <- job:
return nil
case <-gp.ctx.Done():
return context.Canceled
}
}
// Stop stops the goroutine pool
func (gp *GoroutinePool) Stop() {
gp.cancel()
close(gp.jobQueue)
gp.wg.Wait()
}
// SubmitWithTimeout submits a job with timeout
func (gp *GoroutinePool) SubmitWithTimeout(job func(), timeout time.Duration) error {
ctx, cancel := context.WithTimeout(gp.ctx, timeout)
defer cancel()
select {
case gp.jobQueue <- job:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
Lock-Free Data Structures¶
// internal/optimization/lockfree.go
package optimization
import (
"sync/atomic"
"unsafe"
)
// LockFreeStack represents a lock-free stack
type LockFreeStack struct {
head unsafe.Pointer
}
// Node represents a stack node
type Node struct {
value interface{}
next unsafe.Pointer
}
// NewLockFreeStack creates a new lock-free stack
func NewLockFreeStack() *LockFreeStack {
return &LockFreeStack{}
}
// Push pushes a value onto the stack
func (lfs *LockFreeStack) Push(value interface{}) {
node := &Node{value: value}
for {
head := atomic.LoadPointer(&lfs.head)
node.next = head
if atomic.CompareAndSwapPointer(&lfs.head, head, unsafe.Pointer(node)) {
break
}
}
}
// Pop pops a value from the stack
func (lfs *LockFreeStack) Pop() (interface{}, bool) {
for {
head := atomic.LoadPointer(&lfs.head)
if head == nil {
return nil, false
}
node := (*Node)(head)
next := atomic.LoadPointer(&node.next)
if atomic.CompareAndSwapPointer(&lfs.head, head, next) {
return node.value, true
}
}
}
// LockFreeQueue represents a lock-free queue
type LockFreeQueue struct {
head unsafe.Pointer
tail unsafe.Pointer
}
// QueueNode represents a queue node
type QueueNode struct {
value interface{}
next unsafe.Pointer
}
// NewLockFreeQueue creates a new lock-free queue
func NewLockFreeQueue() *LockFreeQueue {
node := &QueueNode{}
return &LockFreeQueue{
head: unsafe.Pointer(node),
tail: unsafe.Pointer(node),
}
}
// Enqueue enqueues a value
func (lfq *LockFreeQueue) Enqueue(value interface{}) {
node := &QueueNode{value: value}
for {
tail := atomic.LoadPointer(&lfq.tail)
tailNode := (*QueueNode)(tail)
if atomic.CompareAndSwapPointer(&tailNode.next, nil, unsafe.Pointer(node)) {
atomic.CompareAndSwapPointer(&lfq.tail, tail, unsafe.Pointer(node))
break
}
}
}
// Dequeue dequeues a value
func (lfq *LockFreeQueue) Dequeue() (interface{}, bool) {
for {
head := atomic.LoadPointer(&lfq.head)
headNode := (*QueueNode)(head)
next := atomic.LoadPointer(&headNode.next)
if next == nil {
return nil, false
}
nextNode := (*QueueNode)(next)
if atomic.CompareAndSwapPointer(&lfq.head, head, next) {
return nextNode.value, true
}
}
}
I/O Optimization¶
Buffered I/O¶
// internal/optimization/buffered_io.go
package optimization
import (
"bufio"
"io"
"os"
)
// BufferedIOOptimizer provides buffered I/O optimization
type BufferedIOOptimizer struct {
bufferSize int
}
// NewBufferedIOOptimizer creates a new buffered I/O optimizer
func NewBufferedIOOptimizer(bufferSize int) *BufferedIOOptimizer {
return &BufferedIOOptimizer{
bufferSize: bufferSize,
}
}
// OptimizeReader optimizes a reader with buffering
func (bio *BufferedIOOptimizer) OptimizeReader(reader io.Reader) *bufio.Reader {
return bufio.NewReaderSize(reader, bio.bufferSize)
}
// OptimizeWriter optimizes a writer with buffering
func (bio *BufferedIOOptimizer) OptimizeWriter(writer io.Writer) *bufio.Writer {
return bufio.NewWriterSize(writer, bio.bufferSize)
}
// OptimizeFileReader optimizes a file reader
func (bio *BufferedIOOptimizer) OptimizeFileReader(filename string) (*bufio.Reader, *os.File, error) {
file, err := os.Open(filename)
if err != nil {
return nil, nil, err
}
reader := bufio.NewReaderSize(file, bio.bufferSize)
return reader, file, nil
}
// OptimizeFileWriter optimizes a file writer
func (bio *BufferedIOOptimizer) OptimizeFileWriter(filename string) (*bufio.Writer, *os.File, error) {
file, err := os.Create(filename)
if err != nil {
return nil, nil, err
}
writer := bufio.NewWriterSize(file, bio.bufferSize)
return writer, file, nil
}
Zero-Copy Operations¶
// internal/optimization/zero_copy.go
package optimization
import (
"io"
"unsafe"
)
// ZeroCopyOptimizer provides zero-copy optimization utilities
type ZeroCopyOptimizer struct{}
// NewZeroCopyOptimizer creates a new zero-copy optimizer
func NewZeroCopyOptimizer() *ZeroCopyOptimizer {
return &ZeroCopyOptimizer{}
}
// CopyBuffer performs zero-copy buffer operations
func (zco *ZeroCopyOptimizer) CopyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
if buf == nil {
buf = make([]byte, 32*1024)
}
return io.CopyBuffer(dst, src, buf)
}
// UnsafeStringToBytes converts string to bytes without copying
func (zco *ZeroCopyOptimizer) UnsafeStringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&s))
}
// UnsafeBytesToString converts bytes to string without copying
func (zco *ZeroCopyOptimizer) UnsafeBytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// FastCopy performs fast copying between slices
func (zco *ZeroCopyOptimizer) FastCopy(dst, src []byte) int {
return copy(dst, src)
}
Database Optimization¶
Connection Pooling¶
// internal/optimization/db_optimization.go
package optimization
import (
"database/sql"
"time"
)
// DatabaseOptimizer provides database optimization utilities
type DatabaseOptimizer struct {
maxOpenConns int
maxIdleConns int
connMaxLifetime time.Duration
connMaxIdleTime time.Duration
}
// NewDatabaseOptimizer creates a new database optimizer
func NewDatabaseOptimizer() *DatabaseOptimizer {
return &DatabaseOptimizer{
maxOpenConns: 100,
maxIdleConns: 10,
connMaxLifetime: time.Hour,
connMaxIdleTime: time.Minute * 10,
}
}
// OptimizeConnectionPool optimizes database connection pool
func (dbo *DatabaseOptimizer) OptimizeConnectionPool(db *sql.DB) {
db.SetMaxOpenConns(dbo.maxOpenConns)
db.SetMaxIdleConns(dbo.maxIdleConns)
db.SetConnMaxLifetime(dbo.connMaxLifetime)
db.SetConnMaxIdleTime(dbo.connMaxIdleTime)
}
// PreparedStatementCache represents a prepared statement cache
type PreparedStatementCache struct {
statements map[string]*sql.Stmt
mutex sync.RWMutex
}
// NewPreparedStatementCache creates a new prepared statement cache
func NewPreparedStatementCache() *PreparedStatementCache {
return &PreparedStatementCache{
statements: make(map[string]*sql.Stmt),
}
}
// Get gets a prepared statement
func (psc *PreparedStatementCache) Get(db *sql.DB, query string) (*sql.Stmt, error) {
psc.mutex.RLock()
stmt, exists := psc.statements[query]
psc.mutex.RUnlock()
if exists {
return stmt, nil
}
psc.mutex.Lock()
defer psc.mutex.Unlock()
// Double-check after acquiring write lock
if stmt, exists := psc.statements[query]; exists {
return stmt, nil
}
stmt, err := db.Prepare(query)
if err != nil {
return nil, err
}
psc.statements[query] = stmt
return stmt, nil
}
// Close closes all prepared statements
func (psc *PreparedStatementCache) Close() error {
psc.mutex.Lock()
defer psc.mutex.Unlock()
for _, stmt := range psc.statements {
stmt.Close()
}
psc.statements = make(map[string]*sql.Stmt)
return nil
}
Performance Monitoring¶
Performance Metrics¶
// internal/monitoring/performance_metrics.go
package monitoring
import (
"sync"
"time"
)
// PerformanceMetrics tracks performance metrics
type PerformanceMetrics struct {
mutex sync.RWMutex
requestCount int64
totalDuration time.Duration
minDuration time.Duration
maxDuration time.Duration
errorCount int64
lastReset time.Time
}
// NewPerformanceMetrics creates new performance metrics
func NewPerformanceMetrics() *PerformanceMetrics {
return &PerformanceMetrics{
lastReset: time.Now(),
}
}
// RecordRequest records a request
func (pm *PerformanceMetrics) RecordRequest(duration time.Duration, isError bool) {
pm.mutex.Lock()
defer pm.mutex.Unlock()
pm.requestCount++
pm.totalDuration += duration
if pm.minDuration == 0 || duration < pm.minDuration {
pm.minDuration = duration
}
if duration > pm.maxDuration {
pm.maxDuration = duration
}
if isError {
pm.errorCount++
}
}
// GetStats returns current statistics
func (pm *PerformanceMetrics) GetStats() PerformanceStats {
pm.mutex.RLock()
defer pm.mutex.RUnlock()
avgDuration := time.Duration(0)
if pm.requestCount > 0 {
avgDuration = pm.totalDuration / time.Duration(pm.requestCount)
}
errorRate := float64(0)
if pm.requestCount > 0 {
errorRate = float64(pm.errorCount) / float64(pm.requestCount)
}
return PerformanceStats{
RequestCount: pm.requestCount,
AvgDuration: avgDuration,
MinDuration: pm.minDuration,
MaxDuration: pm.maxDuration,
ErrorCount: pm.errorCount,
ErrorRate: errorRate,
LastReset: pm.lastReset,
}
}
// Reset resets the metrics
func (pm *PerformanceMetrics) Reset() {
pm.mutex.Lock()
defer pm.mutex.Unlock()
pm.requestCount = 0
pm.totalDuration = 0
pm.minDuration = 0
pm.maxDuration = 0
pm.errorCount = 0
pm.lastReset = time.Now()
}
// PerformanceStats represents performance statistics
type PerformanceStats struct {
RequestCount int64
AvgDuration time.Duration
MinDuration time.Duration
MaxDuration time.Duration
ErrorCount int64
ErrorRate float64
LastReset time.Time
}
Testing Performance¶
Performance Tests¶
// internal/testing/performance_test.go
package testing
import (
"testing"
"time"
)
func BenchmarkStringConcatenation(b *testing.B) {
b.Run("StringBuilder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var builder strings.Builder
builder.WriteString("Hello")
builder.WriteString(" ")
builder.WriteString("World")
_ = builder.String()
}
})
b.Run("StringConcat", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = "Hello" + " " + "World"
}
})
}
func BenchmarkSliceOperations(b *testing.B) {
b.Run("Preallocated", func(b *testing.B) {
for i := 0; i < b.N; i++ {
slice := make([]int, 0, 1000)
for j := 0; j < 1000; j++ {
slice = append(slice, j)
}
}
})
b.Run("Dynamic", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var slice []int
for j := 0; j < 1000; j++ {
slice = append(slice, j)
}
}
})
}
func BenchmarkGoroutinePool(b *testing.B) {
pool := NewGoroutinePool(10)
pool.Start()
defer pool.Stop()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
pool.Submit(func() {
time.Sleep(1 * time.Millisecond)
})
}
})
}
TL;DR Runbook¶
Quick Start¶
// 1. CPU profiling
profiler := NewCPUProfiler()
profiler.Start("cpu.prof")
defer profiler.Stop()
// 2. Memory profiling
memProfiler := NewMemoryProfiler()
memProfiler.StartMemoryProfiling()
defer memProfiler.StopMemoryProfiling("mem.prof")
// 3. Object pooling
pool := NewObjectPool(func() interface{} {
return make([]byte, 1024)
})
// 4. Goroutine pool
goroutinePool := NewGoroutinePool(10)
goroutinePool.Start()
defer goroutinePool.Stop()
Essential Patterns¶
// String optimization
optimizer := NewStringOptimizer()
result := optimizer.ConcatStrings("Hello", " ", "World")
// Slice optimization
sliceOptimizer := NewSliceOptimizer()
slice := sliceOptimizer.PreallocateSlice[int](1000)
// Database optimization
dbOptimizer := NewDatabaseOptimizer()
dbOptimizer.OptimizeConnectionPool(db)
// Performance monitoring
metrics := NewPerformanceMetrics()
metrics.RecordRequest(duration, false)
This guide provides the complete machinery for optimizing Go applications for maximum performance. Each pattern includes implementation examples, benchmarking strategies, and real-world usage patterns for enterprise deployment.