flake-update-20260201

Advanced Golang Workflows

This reference consolidates advanced Go development workflows that are used less frequently.

Core workflows (used frequently) are in separate workflow files:

  • Build (workflows/Build.md)
  • Test (workflows/Test.md)
  • Deps (workflows/Deps.md)
  • Debug (workflows/Debug.md)
  • Lint (workflows/Lint.md)

Benchmark - Performance Benchmarking

Run Go benchmarks to measure and optimize performance.

When to Use

  • “benchmark go code”
  • “measure performance”
  • “run benchmarks”
  • “profile performance”
  • “compare benchmark results”

Quick Commands

Basic Benchmarking

# Run all benchmarks
go test -bench=.

# Run benchmarks in specific package
go test -bench=. ./internal/handlers

# Run specific benchmark
go test -bench=BenchmarkMyFunction

# Run benchmarks matching pattern
go test -bench=BenchmarkUser.*

With Memory Statistics

# Show memory allocations
go test -bench=. -benchmem

# Example output:
# BenchmarkFunction-8   1000000    1234 ns/op    512 B/op    5 allocs/op
#                       ^^^^^^^^^  ^^^^^^^^^^^   ^^^^^^^^^   ^^^^^^^^^^^^^
#                       iterations  ns/operation  bytes/op    allocations/op

Benchmark Time Control

# Run for specific duration (default 1s)
go test -bench=. -benchtime=10s

# Run specific number of iterations
go test -bench=. -benchtime=1000x

# Quick benchmark (shorter duration)
go test -bench=. -benchtime=100ms

Writing Benchmarks

Basic Benchmark

func BenchmarkMyFunction(b *testing.B) {
    // Setup (not timed)
    input := generateInput()

    // Reset timer to exclude setup
    b.ResetTimer()

    // Run function b.N times
    for i := 0; i < b.N; i++ {
        myFunction(input)
    }
}

Benchmark with Table Tests

func BenchmarkParse(b *testing.B) {
    benchmarks := []struct {
        name  string
        input string
    }{
        {"small", "small input"},
        {"medium", "medium sized input string"},
        {"large", strings.Repeat("large ", 1000)},
    }

    for _, bm := range benchmarks {
        b.Run(bm.name, func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                parse(bm.input)
            }
        })
    }
}

Parallel Benchmarks

func BenchmarkParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            // Code to benchmark
            doWork()
        }
    })
}

Reporting Custom Metrics

func BenchmarkCustomMetrics(b *testing.B) {
    var totalBytes int64

    for i := 0; i < b.N; i++ {
        data := processData()
        totalBytes += int64(len(data))
    }

    // Report custom metric
    b.ReportMetric(float64(totalBytes)/float64(b.N), "bytes/op")
}

Profiling with Benchmarks

CPU Profiling

# Generate CPU profile
go test -bench=. -cpuprofile=cpu.prof

# Analyze CPU profile
go tool pprof cpu.prof

# Interactive pprof commands:
# top10       - Show top 10 functions
# list Func   - Show source for function
# web         - Generate graph (requires graphviz)
# pdf         - Generate PDF (requires graphviz)

# Generate CPU profile visualization
go tool pprof -http=:8080 cpu.prof

Memory Profiling

# Generate memory profile
go test -bench=. -memprofile=mem.prof

# Analyze memory profile
go tool pprof mem.prof

# Show allocations
go tool pprof -alloc_space mem.prof

# Show in-use memory
go tool pprof -inuse_space mem.prof

Block Profiling

# Profile blocking operations
go test -bench=. -blockprofile=block.prof

# Analyze
go tool pprof block.prof

Mutex Profiling

# Profile mutex contention
go test -bench=. -mutexprofile=mutex.prof

# Analyze
go tool pprof mutex.prof

Comparing Benchmarks

Using benchstat

# Install benchstat
go install golang.org/x/perf/cmd/benchstat@latest

# Run baseline benchmark
go test -bench=. -count=10 > old.txt

# Make changes and run new benchmark
go test -bench=. -count=10 > new.txt

# Compare results
benchstat old.txt new.txt

# Example output:
# name        old time/op  new time/op  delta
# Function-8  123ns ± 2%   98ns ± 1%   -20.33% (p=0.000 n=10+10)

Manual Comparison

# Run benchmark multiple times for stability
go test -bench=BenchmarkMyFunc -count=5

# Save to file
go test -bench=. -benchmem > benchmark.txt

# Compare manually
# name                time/op    mem/op    allocs/op
# Before: 1234ns     512B       5
# After:  987ns      256B       3
# Delta:  -20%       -50%       -40%

Advanced Techniques

Sub-Benchmarks

func BenchmarkComplexOperation(b *testing.B) {
    sizes := []int{10, 100, 1000, 10000}

    for _, size := range sizes {
        b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) {
            data := make([]int, size)
            b.ResetTimer()

            for i := 0; i < b.N; i++ {
                processSlice(data)
            }
        })
    }
}

Stopping and Starting Timer

func BenchmarkWithSetup(b *testing.B) {
    for i := 0; i < b.N; i++ {
        b.StopTimer()
        // Expensive setup not timed
        data := generateExpensiveData()
        b.StartTimer()

        // This is timed
        process(data)
    }
}

Setting Bytes Processed

func BenchmarkProcess(b *testing.B) {
    data := make([]byte, 1024*1024) // 1MB

    b.SetBytes(int64(len(data)))
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        process(data)
    }
    // Will report MB/s
}

Common Patterns

Benchmark HTTP Handler

func BenchmarkHandler(b *testing.B) {
    handler := setupHandler()
    req := httptest.NewRequest("GET", "/users", nil)

    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        w := httptest.NewRecorder()
        handler.ServeHTTP(w, req)
    }
}

Benchmark with Pool

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func BenchmarkWithPool(b *testing.B) {
    for i := 0; i < b.N; i++ {
        buf := bufferPool.Get().(*bytes.Buffer)
        buf.Reset()

        // Use buffer
        processWithBuffer(buf)

        bufferPool.Put(buf)
    }
}

Benchmark Allocations

func BenchmarkAllocations(b *testing.B) {
    b.ReportAllocs() // Equivalent to -benchmem

    for i := 0; i < b.N; i++ {
        // Code that allocates
        _ = make([]int, 100)
    }
}

Makefile Example

.PHONY: bench
bench:
	go test -bench=. -benchmem ./...

.PHONY: bench-cpu
bench-cpu:
	go test -bench=. -cpuprofile=cpu.prof
	go tool pprof -http=:8080 cpu.prof

.PHONY: bench-mem
bench-mem:
	go test -bench=. -memprofile=mem.prof
	go tool pprof -http=:8080 mem.prof

.PHONY: bench-compare
bench-compare:
	@echo "Running baseline benchmarks..."
	go test -bench=. -count=10 -benchmem > bench-old.txt
	@echo "Make your changes, then run: make bench-compare-new"

.PHONY: bench-compare-new
bench-compare-new:
	@echo "Running new benchmarks..."
	go test -bench=. -count=10 -benchmem > bench-new.txt
	benchstat bench-old.txt bench-new.txt

Best Practices

  1. Run multiple iterations: Use -count=5 or higher for stable results
  2. Use benchstat: For statistical comparison of results
  3. Reset timer after setup: Exclude setup time with b.ResetTimer()
  4. Report allocations: Always use -benchmem to see memory impact
  5. Benchmark real scenarios: Use realistic data and workloads
  6. Run on consistent hardware: Same machine, same load for comparisons
  7. Profile before optimizing: Find bottlenecks with pprof
  8. Benchmark alternatives: Compare different implementations
  9. Document baseline: Save benchmark results in version control
  10. Use sub-benchmarks: Test different input sizes and scenarios

Interpreting Results

Time/Operation

  • Lower is better
  • Represents average time per operation
  • Compare before/after optimization

Allocations/Operation

  • Fewer is better
  • Each allocation has overhead
  • Reducing allocations improves performance

Bytes/Operation

  • Fewer is better
  • Memory allocations cause GC pressure
  • Optimize hot paths to reduce allocations

MB/s (when using SetBytes)

  • Higher is better
  • Throughput measurement
  • Useful for I/O operations

Common Flags

Flag Purpose
-bench=. Run all benchmarks
-benchmem Show memory stats
-benchtime=Xs Run for X seconds
-benchtime=Nx Run N iterations
-count=N Run benchmarks N times
-cpuprofile=file Write CPU profile
-memprofile=file Write memory profile
-blockprofile=file Write block profile
-mutexprofile=file Write mutex profile

Resources


Generate - Code Generation

Code generation with go generate, mocking, and other generation tools.

When to Use

  • “generate go code”
  • “run go generate”
  • “create mocks”
  • “generate from protobuf”
  • “use stringer”

Quick Commands

go generate

# Run generators in current package
go generate

# Run in all packages
go generate ./...

# Verbose output
go generate -v ./...

# Dry run
go generate -n ./...

# Run specific generator
go generate -run mockgen ./...

Common Generators

Stringer (Enum String Methods)

//go:generate stringer -type=Status
type Status int

const (
    Pending Status = iota
    Active
    Completed
)

// Generates: status_string.go with String() method
# Install
go install golang.org/x/tools/cmd/stringer@latest

# Generate
go generate ./...

# Usage
s := Active
fmt.Println(s.String()) // "Active"

mockgen (Mock Interfaces)

//go:generate mockgen -destination=mocks/mock_database.go -package=mocks . Database

type Database interface {
    Get(id string) (*User, error)
    Save(user *User) error
}
# Install
go install github.com/golang/mock/mockgen@latest

# Generate mocks
go generate ./...

# Use in tests
mockDB := mocks.NewMockDatabase(ctrl)
mockDB.EXPECT().Get("123").Return(user, nil)

protoc (Protocol Buffers)

// user.proto
syntax = "proto3";
package api;

message User {
    string id = 1;
    string name = 2;
}
//go:generate protoc --go_out=. --go_opt=paths=source_relative user.proto
# Install
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

# Generate
go generate ./...

Embed (Static Files)

//go:embed templates/*.html
var templates embed.FS

//go:embed static/*
var static embed.FS

func main() {
    data, _ := templates.ReadFile("templates/index.html")
}

Advanced Generators

sqlc (SQL to Go)

# sqlc.yaml
version: "2"
sql:
  - schema: "schema.sql"
    queries: "queries.sql"
    engine: "postgresql"
    gen:
      go:
        package: "db"
        out: "db"
//go:generate sqlc generate

Wire (Dependency Injection)

//go:build wireinject

//go:generate wire

func InitializeApp() (*App, error) {
    wire.Build(
        NewDatabase,
        NewUserService,
        NewApp,
    )
    return nil, nil
}

oapi-codegen (OpenAPI)

//go:generate oapi-codegen -package api -generate types,server openapi.yaml > api.gen.go

Generator Directives

Syntax

//go:generate command arg1 arg2

Examples

// Generate stringer
//go:generate stringer -type=Color

// Multiple commands
//go:generate mockgen -destination=mocks/mock.go . Interface
//go:generate gofmt -w mocks/mock.go

// With environment variables
//go:generate sh -c "VERSION=$(git describe) envsubst < version.tmpl > version.go"

// Conditional generation
//go:generate echo "Generating..."

Makefile Integration

.PHONY: generate
generate:
	go generate ./...
	gofmt -w .

.PHONY: generate-mocks
generate-mocks:
	go generate -run mockgen ./...

.PHONY: generate-proto
generate-proto:
	protoc --go_out=. --go_opt=paths=source_relative api/*.proto

.PHONY: generate-check
generate-check:
	go generate ./...
	git diff --exit-code

.PHONY: clean-generated
clean-generated:
	find . -name "*.gen.go" -delete
	find . -name "*_string.go" -delete

Best Practices

  1. Commit generated code: Makes builds reproducible
  2. Run before building: Ensure generated code is current
  3. Use specific versions: Pin generator tool versions
  4. Document generators: Comment what each directive does
  5. Format generated code: Run gofmt after generation
  6. Check in CI: Verify generated code is up-to-date
  7. Separate generated files: Use .gen.go suffix

CI/CD Integration

GitHub Actions

- name: Install generators
  run: |
    go install golang.org/x/tools/cmd/stringer@latest
    go install github.com/golang/mock/mockgen@latest

- name: Run generators
  run: go generate ./...

- name: Check for changes
  run: |
    if [[ `git status --porcelain` ]]; then
      echo "Generated code is out of date"
      git diff
      exit 1
    fi

Common Patterns

Version Embedding

//go:generate sh -c "echo 'package main\n\nconst Version = \"'$(git describe --tags)'\"' > version.go"

func main() {
    fmt.Println("Version:", Version)
}

Interface Verification

//go:generate go run check_interfaces.go

// Verify implementation
var _ http.Handler = (*MyHandler)(nil)

Template Expansion

//go:generate go run gen_template.go

// gen_template.go
package main

import "text/template"

const tmpl = `// Code generated. DO NOT EDIT.
package {{.Package}}

type {{.Name}} struct {}`

func main() {
    // Generate code from template
}

Resources


Profile - CPU and Memory Profiling

Advanced profiling and performance analysis for Go applications.

When to Use

  • “profile go application”
  • “find performance bottlenecks”
  • “analyze memory usage”
  • “trace execution”
  • “optimize performance”

Types of Profiling

CPU Profiling

# During tests
go test -cpuprofile=cpu.prof -bench=.

# In application
import _ "net/http/pprof"
go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()

# Capture profile
curl http://localhost:6060/debug/pprof/profile?seconds=30 > cpu.prof

# Analyze
go tool pprof cpu.prof

Memory Profiling

# Heap profile
go test -memprofile=mem.prof -bench=.

# From running app
curl http://localhost:6060/debug/pprof/heap > heap.prof

# Analyze
go tool pprof mem.prof

# Allocation profile
go tool pprof -alloc_space mem.prof

Block Profiling

# Profile blocking operations
import "runtime"
runtime.SetBlockProfileRate(1)

# Capture
curl http://localhost:6060/debug/pprof/block > block.prof

# Analyze
go tool pprof block.prof

Mutex Profiling

# Profile mutex contention
import "runtime"
runtime.SetMutexProfileFraction(1)

# Capture
curl http://localhost:6060/debug/pprof/mutex > mutex.prof

# Analyze
go tool pprof mutex.prof

pprof Commands

Interactive Mode

# Top functions by CPU
top
top10

# Show specific function
list functionName

# View as graph (requires graphviz)
web

# View as PDF
pdf

# Show call graph
traces

# Show cumulative time
top -cum

# Filter by function
top grep main

Web UI

# Start web UI
go tool pprof -http=:8080 cpu.prof

# Browse to http://localhost:8080
# Interactive flame graph, top view, source view

Execution Tracing

Generate Trace

# During tests
go test -trace=trace.out

# In application
import "runtime/trace"

f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()

# Or via HTTP
curl http://localhost:6060/debug/pprof/trace?seconds=5 > trace.out

Analyze Trace

# Open trace viewer
go tool trace trace.out

# View in browser - shows:
# - Goroutine execution
# - Network blocking
# - Synchronization blocking
# - System calls
# - GC events

Profiling in Code

Basic CPU Profile

import (
    "os"
    "runtime/pprof"
)

func main() {
    f, _ := os.Create("cpu.prof")
    defer f.Close()

    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // Your code here
}

Heap Profile

import (
    "os"
    "runtime/pprof"
)

func writeHeapProfile() {
    f, _ := os.Create("heap.prof")
    defer f.Close()
    pprof.WriteHeapProfile(f)
}

HTTP pprof Server

import (
    "net/http"
    _ "net/http/pprof"
)

func main() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // Your application code
}

// Access profiles at:
// http://localhost:6060/debug/pprof/

Analyzing Results

CPU Profile Interpretation

  • flat: Time spent in function itself
  • cum: Cumulative time (including callees)
  • %: Percentage of total time

Memory Profile Interpretation

  • alloc_space: Total allocated bytes
  • alloc_objects: Total allocated objects
  • inuse_space: Currently in-use bytes
  • inuse_objects: Currently in-use objects

Finding Hot Paths

# Top CPU consumers
go tool pprof -top cpu.prof

# Top memory allocators
go tool pprof -top mem.prof

# Call graph
go tool pprof -web cpu.prof

Benchmarking with Profiling

# CPU + memory profile
go test -bench=. -cpuprofile=cpu.prof -memprofile=mem.prof -benchmem

# Compare before/after
benchstat old.txt new.txt

Continuous Profiling

Datadog Example

import "gopkg.in/DataDog/dd-trace-go.v1/profiler"

err := profiler.Start(
    profiler.WithService("myapp"),
    profiler.WithEnv("production"),
)
defer profiler.Stop()

Common Optimizations

Reduce Allocations

// Before: Creates new slice each time
func process(data []int) []int {
    result := make([]int, 0)
    for _, v := range data {
        result = append(result, v*2)
    }
    return result
}

// After: Preallocate with capacity
func process(data []int) []int {
    result := make([]int, 0, len(data))
    for _, v := range data {
        result = append(result, v*2)
    }
    return result
}

Use sync.Pool

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func process() {
    buf := bufferPool.Get().(*bytes.Buffer)
    defer bufferPool.Put(buf)
    buf.Reset()

    // Use buffer
}

Avoid String Concatenation in Loops

// Slow
var s string
for _, item := range items {
    s += item  // Creates new string each time
}

// Fast
var b strings.Builder
for _, item := range items {
    b.WriteString(item)
}
s := b.String()

Makefile Example

.PHONY: profile-cpu
profile-cpu:
	go test -bench=. -cpuprofile=cpu.prof
	go tool pprof -http=:8080 cpu.prof

.PHONY: profile-mem
profile-mem:
	go test -bench=. -memprofile=mem.prof
	go tool pprof -http=:8080 mem.prof

.PHONY: trace
trace:
	go test -trace=trace.out
	go tool trace trace.out

.PHONY: profile-serve
profile-serve:
	@echo "Starting pprof server on :6060"
	@echo "CPU: curl http://localhost:6060/debug/pprof/profile?seconds=30 > cpu.prof"
	@echo "Heap: curl http://localhost:6060/debug/pprof/heap > heap.prof"
	go run -tags pprof main.go

Best Practices

  1. Profile in production-like environment: Real data, real load
  2. Focus on hot paths: Optimize the 20% that matters
  3. Measure before and after: Use benchstat for comparisons
  4. Profile different aspects: CPU, memory, blocking, contention
  5. Use flame graphs: Visual representation is powerful
  6. Sample for sufficient time: 30+ seconds for CPU profiles
  7. Check GC impact: High GC time indicates memory issues
  8. Monitor in production: Continuous profiling catches regressions

Resources