Key points
- No force pragma exists. Go intentionally provides only
//go:noinlineto disable inlining; there is no//go:inlineor C/C++-style__attribute__((always_inline)). - Inlining is automatic and per-function. The gc compiler uses an SSA inlining cost budget (empirically ~80). Functions whose body cost exceeds the budget are rejected; each function is decided independently, so calling a non-inlinable function does NOT block the caller from being inlined.
- Concrete rejection triggers verified on Go 1.26.5: top-level
defer(unhandled op DEFER),recoverinside a closure (call to recover), andreflectcalls that blow past the cost budget. - Interface methods can inline. When the call site knows the concrete type, devirtualization allows
Cat.Sing-style interface methods to be inlined. - Small loops and switches are fine; large loops/deep branches exceed the budget.
- Receiver type (value vs pointer) doesn't matter directly, but copying a large struct via a value receiver adds to the cost.
go build -gcflags="-m" .— look forcan inline <Func>go build -gcflags="-m -m" .— see the rejection reason (cannot inline <Func>: <reason>)go build -gcflags="-l" .— disable inlining globally for debugging (stack-lto disable more passes)- "Calling a non-inlineable function blocks you" — false. A wrapper that calls a
//go:noinlinefunction can still be inlined. - "Since Go 1.14 all top-level defers inline" — false on Go 1.26.5; top-level
deferstill producesunhandled op DEFER. Only the closure body insidedeferis eligible, and even then it cannot containrecover. - "
//go:inlinecan force inlining" — does not exist. The engineering reason: forced inlining slows compilation, bloats binaries, and breaks the compiler's de-inlining debugging capability. - Inlining ≠ performance. It removes call overhead and opens doors for constant propagation and escape analysis, but it grows the binary and can push hot functions past the budget. Profile before optimizing.
//go:noinlineis a debugging/profiling probe, not an optimization tool. For extreme hot paths, rewrite with optimized libraries (e.g.,kelindar/simd) or hand-written assembly rather than forcing inlining.- The gc compiler does not auto-vectorize ordinary loops; SIMD only exists in selected standard-library functions.
Practical recipe
1. Keep the function body small enough to stay under the cost budget.
2. Remove top-level defer, recover, and reflect from hot paths.
3. Verify with:
Note: in Go 1.26 the -m=2 output format changed, so single-level -m -m is the most stable option.
Three myths debunked by testing
Caveats
Methodology
All claims are verified against Go 1.26.5 using go build -gcflags="-m -m" . against a two-file demo (main.go + blocked.go) covering both inlinable and rejected cases, so the outputs are reproducible.
One-line takeaway: write small functions → confirm can inline with -m → if rejected, shrink the body and remove top-level defer/recover/reflect → for ultimate speed, reach for hand-written assembly or an existing SIMD library.