What are the performance costs of errors, defer and panic/recover in hot paths?
Question 330HardGo 1.22 to 1.25
- Sentinel errors are created once at package init. Returning one is just copying an interface value, with no allocation.
fmt.Errorfformats a string and allocates the wrapper (plus the string) on every call. In a tight loop where errors are frequent (a parser, a cache miss path), prefer a sentinel or a small struct error, and build the message lazily inError().- Boxing: storing a non-pointer struct in an
errorinterface usually allocates. A pointer type avoids the extra copy, although the&T{}itself may escape to the heap. errors.Is/Aswalk the chain and may use reflection-like type checks (Asusesreflectlite). This is cheap but not free. Deep chains or hot comparisons cost more than a plain==.defer: since Go 1.14 most defers are open-coded (inlined at the function exit) and cost about as much as a normal call. That applies when the function has at most 8 defers and none of them is in a loop. A defer in a loop falls back to the slower heap/stack-record path.panic/recoveris far slower than returning an error: it unwinds the stack, runs deferred calls through the runtime, and a function that containsrecoverhandling is harder to optimize. Never use it for control flow in the normal path.
var errMiss = errors.New("cache miss") // no allocation per return
func (c *Cache) Get(k string) (V, error) {
if v, ok := c.m[k]; ok {
return v, nil
}
return V{}, errMiss // cheap; callers use errors.Is / ==
// return V{}, fmt.Errorf("miss %q", k) // allocates on every miss
}
Measure before optimizing: go test -bench . -benchmem shows allocations per op, and go build -gcflags=-m shows what escapes. In most code the clarity of wrapped errors is worth the cost. Optimize only the paths where profiling shows it matters.
More on Error Handling & panics
- Q328What happens when a deferred function panics while the goroutine is already panicking?
- Q329What happens if the function passed to
sync.Once.Dopanics? How dosync.OnceFuncandsync.OnceValuediffer?