Understanding Go Function Pointers and Their Performance Impact
The article explains how Go’s function values introduce an extra indirection and trigger escape analysis, making indirect calls slower than C pointers, and offers optimization tactics such as avoiding function‑pointer calls, using switch‑based dispatch, or applying the unsafe noescape trick when safe.
This article examines the performance implications of Go's function values (often called "function pointers") and provides concrete optimization strategies based on assembly inspection.
Background
When micro‑optimising Go code, developers notice that function calls are slower than in C/C++ and that the penalty grows when pointer arguments are involved. The article analyses why and how to mitigate the overhead.
1. Function call implementation in C
Example C code:
int Add(int a, int b) { return a + b; }Generated assembly (x86‑64, Linux):
Add: lea eax, [rdi+rsi] retAccording to the calling convention, the first two integer arguments are passed in RDI and RSI , and the result is returned in EAX .
2. Function call implementation in Go
Simple Go function:
func Add(a, b int) int { return a + b }Generated assembly (Go 1.17+, x86‑64):
main.Add STEXT nosplit size=4 args=0x10 locals=0x0 funcid=0x0 align=0x0 0x0000 00000 (
:4) ADDQ BX, AX 0x0003 00003 (
:4) RETThe registers differ ( AX , BX ) but the overall cost is similar.
3. Returning a function value
Go code that returns a closure:
func MakeAdd() func(int, int) int { return func(a, b int) int { return a + b } }Generated assembly:
main.MakeAdd STEXT nosplit size=8 args=0x0 locals=0x0 funcid=0x0 align=0x0 0x0000 00000 (
:15) LEAQ main.Add·f(SB), AX 0x0007 00007 (
:15) RETThe function value does not point directly to the code; it points to a small data object ( ·f ) that contains the real address.
4. Indirect call via a function pointer
Calling a function through a pointer:
func CallAddPtr(add func(int, int)) { add(1, 2) }Generated assembly (simplified):
main.CallAddPtr STEXT nosplit size=51 args=0x8 locals=0x18 funcid=0x0 align=0x0 ... MOVQ (DX), CX // load the real function address CALL CX // indirect callCompared with the direct call, an extra memory load is required, and the compiler can no longer prove that the argument does not escape.
5. Escape analysis impact
When a variable is passed to a function pointer, the compiler conservatively assumes the address may be stored for later use, causing the variable to escape to the heap. Example:
func CallSetPtr(set func(*int)) { a := 0; set(&a) }Generated assembly shows a heap allocation via runtime.newobject before the indirect call, confirming the escape.
Escape analysis can be inspected with the -m flag, and the performance penalty of heap allocation is often larger than the extra instruction overhead.
6. Mitigating escape with noescape
Go's runtime provides a helper that hides a pointer from escape analysis:
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}By applying a trivial XOR, the compiler treats the result as unrelated to the original pointer, preventing escape. This must be used with care to ensure the pointer is not stored for later use.
7. Switch‑based dispatch
If the set of possible function pointers is small, a switch statement can replace indirect calls, eliminating the extra load and escape. The Go time package uses this technique for formatting.
Conclusion
Go's function‑value implementation adds an extra level of indirection and can trigger escape analysis, making it slower than equivalent C function pointers. For most code, readability should dominate, but when micro‑optimising, consider avoiding indirect calls, using switch dispatch, or the noescape trick where safe.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.