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.

Reply via email to