Skip to content

Notes — Module 16: Performance and Profiling

These are your personal study notes. Write freely and honestly. Incomplete notes are fine — they show where your understanding still needs work. Return to this file to add insights as they develop over time.

Module: [[go/16. Performance and Profiling]] Topic: [[go]] Date started: YYYY-MM-DD Status: In progress


Concept Map

Sketch how the concepts in this module relate to each other. Fill in the Mermaid diagram.

mindmap
  root((Performance and Profiling))
    Mindset
      measure before optimizing
      optimize the right thing
      correctness first
    Benchmarking
      testing.B loop
      -benchmem (allocs/op, B/op)
      -count=N for repeatability
      benchstat for comparison
      pitfalls: DCE, missing ResetTimer
    pprof profiles
      CPU (where time is spent)
      heap (where memory is allocated)
      goroutine (all goroutine stacks)
      block (channel/mutex waits)
      mutex (lock contention)
    Capturing profiles
      go test -cpuprofile / -memprofile
      runtime/pprof (batch programs)
      net/http/pprof (live services)
    go tool pprof
      top / top -cum
      list FuncName
      web (flamegraph)
    Escape analysis
      go build -gcflags=-m
      stack vs heap allocation
      causes of escape
      GC pressure link
    Concrete optimizations
      preallocate slices/maps
      strings.Builder
      avoid string/byte copies
      sync.Pool
      avoid interface boxing
    GC tuning
      GOGC (frequency)
      GOMEMLIMIT (hard cap)
      debug.SetGCPercent
    PGO
      default.pgo file
      automatic since 1.21
      devirtualization

Alternative: draw this on paper, photo it, and link the image here.


Key Insights

The "aha moments" — the things that, once understood, made the rest clear. Be specific: "I finally understood X because Y" is more useful than "X makes sense".

  1. Profile first, always: I finally understood that the profiler almost always contradicts my intuition about what's slow. The heat map analogy clicked — you can only cool a room if you know which room is hot.
  2. Allocations are not free: The connection between heap allocations and GC work became concrete when I saw runtime.mallocgc near the top of a CPU profile. Every allocation is a future GC job.
  3. Add insights as you discover them

My Understanding

Explain the core concepts in your own words, as if teaching them to someone else. If you can't explain it simply, you don't understand it well enough yet.

The Performance Mindset

Your explanation here

What I'm still unsure about: (e.g., how to know when I've optimized "enough")

Benchmarking and benchstat

Your explanation here

What I'm still unsure about: (e.g., how many -count repetitions are needed for statistical significance)

pprof profiles

Your explanation here

What I'm still unsure about: (e.g., the difference between flat and cum time in top output)

Escape Analysis

Your explanation here

What I'm still unsure about: (e.g., when interface boxing specifically triggers an escape)

GC Tuning

Your explanation here

What I'm still unsure about: (e.g., how GOGC and GOMEMLIMIT interact when both are set)


Connections to Other Topics

How does this module connect to things you already know?

This module's concept Connects to How
Heap allocations and GC pressure [[memory-management]] Go's tricolor GC is the reason allocation count matters; every heap object is scanned by the GC
Benchmarking with testing.B [[go/11. Testing and Benchmarking]] The profiling tools in this module are extensions of the benchmark infrastructure from Module 11
sync.Pool [[go/9. Concurrency]] Pool is a sync primitive; understanding that it is goroutine-safe but not a persistent cache requires concurrency knowledge
GOGC / GOMEMLIMIT [[go/17. Runtime Internals and the Memory Model]] GC tuning only makes sense once you understand how the GC works internally
Preallocating slices [[go/4. Composite Types]] make([]T, 0, n) pre-allocates the backing array; understanding append's growth strategy explains why this matters

Questions That Arose

Log questions as they appear. Don't stop to answer them now — just capture them. Then move the serious ones to QUESTIONS.md.

  • Does sync.Pool guarantee that the object I Put in is the same one I Get back? → added to QUESTIONS.md as Q001
  • How does the compiler decide the inlining budget? Is there a way to check it per function? → might be in go build -gcflags=-m -m output
  • Can I use net/http/pprof with a custom HTTP mux (like chi or gorilla/mux)? → likely yes, need to confirm

Code Snippets Worth Remembering

Patterns, idioms, or examples that captured something important.

Benchmark with DCE protection and ResetTimer

var sink any // package-level sink prevents dead-code elimination

func BenchmarkX(b *testing.B) {
    input := setupExpensiveInput()
    b.ResetTimer() // exclude setup from measurement
    var result []byte
    for range b.N {
        result = processInput(input)
    }
    sink = result // prevent DCE
}

Why I'm saving this: The two-line pattern (sink + ResetTimer) appears in every correct production benchmark. Missing either one makes the benchmark untrustworthy.


Capture and read a heap profile in one shot

go test -bench=BenchmarkX -memprofile=mem.out ./...
go tool pprof -http=:8080 mem.out

Why I'm saving this: The -http flag opens the browser UI directly — no interactive shell needed. Faster for a quick look.


strings.Builder with Grow

var sb strings.Builder
sb.Grow(estimatedBytes) // single allocation for the final string
for _, s := range parts {
    sb.WriteString(s)
}
return sb.String()

Why I'm saving this: Grow reduces strings.Builder to exactly one allocation (the final buffer). Without it, Builder still amortizes but may do 2–3 internal reallocations.


GOMEMLIMIT + GOGC for containerized services

# In a Kubernetes pod spec or Dockerfile CMD:
GOMAXPROCS=4 GOGC=200 GOMEMLIMIT=450MiB ./myservice

Why I'm saving this: Higher GOGC reduces GC CPU cost; GOMEMLIMIT guards against OOM in a container with a 512Mi limit. This is the production-recommended pairing.


What Tripped Me Up

Mistakes I made, misconceptions I had, things that confused me more than they should have. Being honest here helps you later.

  • Dead-code elimination in benchmarks — I initially thought assigning to _ was enough to prevent DCE. It is not — _ is a complete discard. You need a package-level sink variable.
  • flat vs cum in pprof top — I initially read cum as "the function is slow." It actually means "this function and everything it calls." A function with high cum but low flat is a caller of slow code, not slow itself.

Summary in My Own Words

Write a 3–5 sentence summary of this entire module without looking at any notes. If you can't do this, you need more study time.

Write your summary here after completing the module.


Last updated: YYYY-MM-DD