Measuring Size-Specialized Malloc with GOEXPERIMENT
Measuring Size-Specialized Malloc with GOEXPERIMENT
Thesis
Go 1.27 already documents size-specialized mallocgc for known sizes ≤80 bytes. This chapter is the measurement companion: the same microbenchmarks with the default build versus GOEXPERIMENT=nosizespecializedmalloc, so you can see the win on your CPU instead of trusting the blog’s “up to ~30%” alone. The neighbouring chapter maps size classes; here we only time them.
Mental model
compiler knows size ≤ 80B
│
├─ default 1.27 → call mallocgcSmall…SCn directly
└─ GOEXPERIMENT=nosizespecializedmalloc → generic mallocgc path
Opt-out is a bisect tool. The Go team expects the experiment knob to go away in 1.28.
Worked examples
Verified with Go 1.27.2 linux/amd64 on an Intel Xeon guest.
Case 1: tiny struct vs 80B vs 96B slices
// main_test.go
package main
import "testing"
type tiny struct{ a, b, c, d int64 } // 32 bytes
func BenchmarkTinyAlloc(b *testing.B) {
b.ReportAllocs()
var sink *tiny
for i := 0; i < b.N; i++ {
sink = &tiny{1, 2, 3, 4}
}
_ = sink
}
func BenchmarkSlice80(b *testing.B) {
b.ReportAllocs()
var sink []byte
for i := 0; i < b.N; i++ {
sink = make([]byte, 80)
}
_ = sink
}
func BenchmarkSlice96(b *testing.B) {
b.ReportAllocs()
var sink []byte
for i := 0; i < b.N; i++ {
sink = make([]byte, 96)
}
_ = sink
}go test -bench=. -benchmem -count=5
GOEXPERIMENT=nosizespecializedmalloc go test -bench=. -benchmem -count=5Representative lines from this run (5 counts each; lower ns/op is better):
| Benchmark | Default (ns/op) | nosizespecialized (ns/op) |
|---|---|---|
| TinyAlloc (32 B) | ~12.6–13.8 | ~17.6–18.4 |
| Slice80 | ~25–27 | ~28–33 |
| Slice96 (above 80) | ~30–34 | ~31–32 |
The 32-byte path shows the clearest gap (~30% here). 96 bytes sits outside the specialized window and barely moves — a useful control.
Trap
Declaring victory from one microbenchmark. Size specialization is a small overall win (~1% on alloc-heavy programs per the release notes). If your profile is dominated by large buffers or stack allocation, you will see noise, not a rewrite-worthy speedup.
Boring rule
Stay on Go 1.27+ and let the compiler specialize. Reach for GOEXPERIMENT=nosizespecializedmalloc only when bisecting a suspected allocator regression, then file an issue if you need the knob to stay.
Try this
- Add a
BenchmarkMakeNwherenis a runtime variable and compare. - Run with
-benchtime=2son a quiet machine and compute medians. - Cross-check against
go tool pprofalloc profiles on a real service.
Sources
- Go 1.27 release notes — runtime
- Size-specialized malloc blog (Go 1.27 allocator posts)
- Sibling chapter
25d-size-specialized-malloc.qmd(class table)