Thanks Robert. I don't think it's the value that's causing this....I still get the allocation if I remove the value from the equation. It's something to do with the root level having children.
The escape analysis from the refactored code below is: go build -gcflags='-m=2' . ./alloc.go:12:27: parameter y leaks to {heap} with derefs=0: ./alloc.go:12:27: flow: {heap} = y: ./alloc.go:12:27: from y.key (dot) at ./alloc.go:19:6 *./alloc.go:12:27: from fn(y.key) (call parameter) at ./alloc.go:19:4* Strings can't reference anything, AFAIK. BenchmarkAllocations0-2 97209274 28.76 ns/op 0 B/op 0 allocs/op BenchmarkAllocations1-2 1395814 796.9 ns/op 1152 B/op 1 allocs/op type ( Y struct { key string children []Y unused [128]uint64 // Demonstrates that the whole struct is placed on the heap! } useKeyFunc = func(key string) ) func scanY(fn useKeyFunc, y Y) { if y.children != nil { for i := 0; i < len(y.children); i++ { scanY(fn, y.children[i]) } return } fn(y.key) } func printKey(key string) { } func Benchmark_ZeroAllocations(b *testing.B) { b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { scanY( printKey, Y{ key: "root", children: []Y{}, }, ) } }) } func Benchmark_OneAllocation(b *testing.B) { b.ReportAllocs() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { scanY( printKey, Y{ key: "root", children: []Y{ { key: "level1", children: nil, }, }, }, ) } }) } On Saturday, August 31, 2024 at 5:02:28 AM UTC+12 robert engels wrote: > because when you only access an int that is passed by value in the > function it knows it can’t escape. > > if you pass the string/value than the the variable can escape, and since > the value could point back to the struct itself, it can escape, meaning the > entire struct needs to be on the heap. > > > On Aug 30, 2024, at 3:43 AM, rkerno <rik.ke...@projectcatalysts.com> > wrote: > > Hi Everyone, > > https://gist.github.com/rkerno/c875609bdeb2459582609da36b54bf72 > > I'm struggling to wrap my head around the root cause of this issue. > > Accessing certain fields of a struct triggers go to allocate the complete > struct on the heap. The key ingredients are: > > - A struct with a contained slice of structs defined inline > - Calls to functions via a function pointer > > What follows is a contrived example to demonstrate / investigate the > problem. > > const ( > // > // When requireZeroAllocations == true... > // > // BenchmarkAllocations-2 27763290 52.28 ns/op > 0 B/op 0 allocs/op > // > // When requireZeroAllocations == false... > // > // BenchmarkAllocations-2 14403922 1532.00 > ns/op 2304 B/op 2 allocs/op > // > requireZeroAllocations = false > ) > > type ( > X struct { > id uint > key string > value any > children []X > useId useIdFunc > useX useXFunc > useKey useKeyFunc > useValue useValueFunc > unused [128]uint64 // Demonstrates that the whole struct is placed on > the heap! > } > useIdFunc = func(id uint) > useXFunc = func(key string, value any) > useKeyFunc = func(key string) > useValueFunc = func(value any) > ) > > func scanX(x X) { > if len(x.children) > 0 { > for i := 0; i < len(x.children); i++ { > scanX(x.children[i]) > } > return > } > > x.useId(x.id) > if !requireZeroAllocations { > // Why can we access the id field without an allocation, but as soon as we > access > // the string or any fields the entire struct is placed on the heap? > x.useKey(x.key) > x.useX(x.key, x.value) > x.useValue(x.value) > } > } > > func printId(id uint) { > } > func printX(key string, value any) { > } > func printKey(key string) { > } > func printValue(value any) { > } > > > The benchmark used to demonstrate the behaviour is: > > func BenchmarkAllocations(b *testing.B) { > b.ReportAllocs() > b.ResetTimer() > b.RunParallel(func(pb *testing.PB) { > for pb.Next() { > scanX( > X{ > id: 0, > key: "root", > value: nil, > children: []X{ > { > id: 1, > key: "level1", > value: nil, > children: []X{ > { > id: 2, > key: "k1", > value: "v1", > children: nil, > useId: printId, > useX: printX, > useKey: printKey, > useValue: printValue, > }, > }, > }, > }, > }, > ) > } > }) > } > > Any insights or ideas why go exhibits this behaviour would be appreciated. > > Cheers, > Rik > > -- > You received this message because you are subscribed to the Google Groups > "golang-nuts" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to golang-nuts...@googlegroups.com. > To view this discussion on the web visit > https://groups.google.com/d/msgid/golang-nuts/3ff0270c-caf0-4a23-9cdf-08bf40e83a33n%40googlegroups.com > > <https://groups.google.com/d/msgid/golang-nuts/3ff0270c-caf0-4a23-9cdf-08bf40e83a33n%40googlegroups.com?utm_medium=email&utm_source=footer> > . > > > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/8e726419-e984-4873-a2e9-d6a6eb4794a3n%40googlegroups.com.