# Go - Hot-Path Micro-Optimizations
A deferred unlock, a format call, or a variadic slice that crosses a linkname boundary costs nanoseconds per call. Those nanoseconds dominate when the path runs millions of times per second. Every rule on this page comes from a measured regression or a measured win in a Go tracing-library hot path.
## Key points
- A getter that performs one map read under `mu.RLock()` plus `defer mu.RUnlock()` pays the defer and unlock cost on every call. When callers call the getter millions of times per second, that cost is non-trivial. The same getter returned the field directly with no lock and avoided the cost entirely.
- Tag propagation that acquires a shared read lock on every inject call holds the lock for time that scales linearly with the tag count. A benchmark setup with 100 tags made the lock hold large enough to explain an entire W3C tag-injection regression.
- An atomic store that replaces a direct field write on a handle shared by many goroutines adds measurable cache-line pressure. The pressure appears even when the field is not the bottleneck.
- Direct `+` concatenation beats `strings.Join` on static pieces even when the compiler stack-allocates the slice literal. The compiler folds consecutive `+` operands into a single allocation. `Join` iterates per element and burns CPU on that iteration. One request-URL builder replaced `Join` with `+` and dropped geomean ns/op by 36.55 percent with zero change in alloc count.
- `http.Header` is a map keyed by the canonical MIME form of the key. A case-insensitive `strings.EqualFold` scan over every key is replaceable with one direct map index. A lookup that computed `textproto.CanonicalMIMEHeaderKey` once at `init()` became about 15.6 times faster on the miss path, with zero allocations.
- `fmt.Sprintf("%016x", ...)` costs about 47 ns and one allocation. A hand-written stack-array hex fill does the same work in about 7 ns with zero allocations. The win stayed invisible inside a 22-allocation W3C extraction benchmark until a focused micro-benchmark isolated the operation. `perfsprint` does not flag zero-padded hex because `strconv` has no equivalent, so linters do not catch this pattern.
- A `//go:linkname` boundary forces a variadic options slice to the heap. A library replaced an exported function call with a bodyless `//go:linkname` to the same code, and the allocation count regressed. The boundary caused the regression, not the API change. Writing the options field by field into a config struct kept the slice away from the boundary and restored the exact allocation count.
## How to apply
1. Isolate the suspect operation in a focused micro-benchmark before you optimize. A nanosecond-scale win inside a 22-allocation benchmark stays invisible until you isolate the operation.
2. Remove `defer mu.RUnlock()` from getters that perform one map read. Return the field directly.
3. Acquire a shared read lock once per operation. Do not re-acquire per element when the element count scales.
4. Keep hot-path field writes direct. Add an atomic store only after a benchmark shows contention on the field.
5. Replace `strings.Join` on static pieces with direct `+` concatenation.
6. Compute `textproto.CanonicalMIMEHeaderKey` once at `init()` and index `http.Header` directly. Do not scan keys with `strings.EqualFold`.
7. Replace `fmt.Sprintf` zero-padded hex with a stack-array fill. Do not expect `perfsprint` to flag the pattern.
8. Keep variadic slices away from `//go:linkname` boundaries. Pass a config struct and write its fields directly.
## Observations
- [concurrency] `defer mu.RUnlock()` in a getter that performs one map read adds non-trivial overhead at millions of calls per second. Return the field directly with no lock. #go (source: agent sessions, 2026-01)
- [concurrency] A read lock acquired per inject call over propagating tags scales lock hold time linearly with the tag count. The lock hold explained an entire benchmark regression at 100 tags. #go (source: agent sessions, 2026-01)
- [concurrency] An atomic store that replaces a direct field write on a handle shared by many goroutines adds cache-line pressure even when the field is not the bottleneck. #go (source: agent sessions, 2026-01)
- [performance] Direct `+` beats `strings.Join` on static pieces. One URL builder dropped geomean ns/op by 36.55 percent with no alloc change. #go (source: agent sessions, 2026-07)
- [performance] One `textproto.CanonicalMIMEHeaderKey` call at `init()` replaces a case-insensitive header-key scan. The miss path became about 15.6 times faster with zero allocations. #go (source: agent sessions, 2026-07)
- [performance] A stack-array hex fill replaces `fmt.Sprintf("%016x")` at about 7 ns versus 47 ns, with zero allocations. #go (source: agent sessions, 2026-07)
- [allocations] A `//go:linkname` boundary forces a variadic options slice to the heap. Field-by-field writes into a config struct restored the exact allocation count. #go (source: agent sessions, 2026-10)
## Relations
- relates_to [[resources/documentation/Go/Benchmarks/Go Benchmarks - Statistics and benchstat]] (benchstat separates real micro-optimization wins from noise)
- relates_to [[resources/documentation/Go/Go - Concurrency, Locks, and checklocks]] (lock and defer costs on hot getters)
- relates_to [[resources/documentation/Go/Benchmarks/Go Benchmarks - Compiler Honesty and Dead Code Elimination]] (compiler allocation and folding behavior underpins the Join and linkname rules)
## Sources
Personal agent-session notes, January 2026 to October 2026