What techniques do you use to reduce allocations in a hot path?
Question 344MediumGo 1.22 to 1.25
- Measure first:
go test -bench . -benchmem, thenpprof -sample_index=alloc_space. - Preallocate:
make([]T, 0, n)andmake(map[K]V, n), and usestrings.Builder.Grow. - Reuse buffers: take a caller-supplied
dst []bytein thestrconv.AppendInt/AppendFormatstyle, reset slices withs = s[:0], or usesync.Pool. - Avoid interface boxing in hot loops. Generics with concrete types, or typed functions, avoid converting to
any. - Pass and return values instead of pointers when small: returning a struct by value often keeps it on the stack.
- Avoid string/[]byte round trips. Use
bytesfunctions directly,strconv.AppendX, orio.WriteString. - Reduce pointers:
[]Tinstead of[]*T, and indices instead of pointers in large graphs. This cuts GC scan work too. - Keep constructors inlinable, so the caller's escape analysis applies to what they return.
// Allocates on every call
func keyA(id int, name string) string { return fmt.Sprintf("%d:%s", id, name) }
// Appends into a caller-provided buffer: zero allocs when reused
func keyB(dst []byte, id int, name string) []byte {
dst = strconv.AppendInt(dst, int64(id), 10)
dst = append(dst, ':')
return append(dst, name...)
}
What the interviewer is looking for: an approach driven by profiling, not premature micro-optimisation. Fewer allocations mean less GC CPU and fewer mark assists, and therefore better tail latency.
More on Memory, GC & Runtime Internals
- Q342How do you read a line of
GODEBUG=gctrace=1output? - Q343Why is the process RSS much larger than the heap in use? How does Go return memory to the OS?
- Q345How does
sync.Poolbehave, and what are its pitfalls? - Q346How do you measure and assert allocation counts?
- Q347When does converting between
stringand[]byteNOT allocate? - Q348What does this print on a 64-bit platform, and why?