Go 1.27 makes allocations of 80 bytes or fewer 20–30% faster, which works out to roughly 1% on allocation-heavy programs. Michael Matloob’s write-up on the Go blog explains the mechanism: instead of one general allocator entry point, the runtime now generates a specialized function per span class.

The mechanics:

  • Small allocations are bucketed into size classes — 1–8, 9–16, 17–24, 25–32, 33–48, 49–64, 65–80 bytes — each with its own free list of objects sized to the class maximum.
  • A span class encodes both facts the allocator needs: sizeClass<<1 | noPointers, since the GC needs to know whether the block holds pointers.
  • The runtime emits one specialized variant per span class. mallocgcSmallNoScanSC3 is small, pointer-free, size class 3. Where the compiler knows the span class at compile time, it inserts a direct call to the specialized function instead of calling newobject.
  • Anything unusual — GC active, or a dynamically sized allocation the compiler can’t resolve — falls back to generic mallocgc, which still has to dispatch to the specialized function dynamically. So the specialized versions have to be fast enough to win even with that overhead.

Why stop at 80 bytes? Two forces. Specialization buys less as sizes grow, because the allocation starts being dominated by clearing memory. And every added function inflates binaries and competes for instruction cache — mallocgc is in the icache constantly, and crowding out user code cancels the gain. They benchmarked across size classes and picked 80 bytes as the sweet spot.

What actually makes them faster:

  • Constant-size clears let the compiler emit clearing instructions directly instead of calling memclrNoHeapPointers — the biggest single win.
  • No runtime span-class calculation, and constants enable faster bookkeeping, including pointer-location marking.
  • Helper functions get manually inlined past the compiler’s size limits.

The maintenance problem is handled with an AST-based inliner built on go/ast and golang.org/x/tools/go/ast/astutil, so the shared parts of the variants can’t drift apart across ~14 generated copies.

For anyone writing Go, the takeaway is small: build with Go 1.27 and the gains arrive on their own, with the most benefit on the 16- and 24-byte sizes — interfaces and strings (two words), slices (three) — because those are the most common allocations in real programs. There is an escape hatch (GOEXPERIMENT=nosizespecializedmalloc), and the feature was held from Go 1.26 to a later release on purpose to tune code size down first.