Thanks Robert, I've come to the same conclusion.  I'm guessing the approach 
simplifies and speeds up the escape analysis, though appears to be a 
significant limitation.

The following extract from escape.go seems to apply.

https://tip.golang.org/src/cmd/compile/internal/escape/escape.go

// Every Go language construct is lowered into this representation,
// generally without sensitivity to flow, path, or context; and
// *without distinguishing elements within a compound variable*. For
// example:
//
//     var x struct { f, g *int }
//     var u []*int
//
//     x.f = u[0]
//
// is modeled simply as
//
//     x = *u
//
// That is, we don't distinguish x.f from x.g, or u[0] from u[1],
// u[2], etc. However, we do record the implicit dereference involved
// in indexing a slice.

Thanks for your help on this.

Cheers,
Rik

On Saturday, August 31, 2024 at 10:45:46 AM UTC+12 Robert Engels wrote:

> Yes, but the called function can retain a reference to the string - 
> strings are pointers. it cannot retain a reference to the scalar. 
>
> I’m guessing if any of the elements can be retained then the entire 
> structure is allocated on the heap - I don’t think it has to but it is 
> probably the current implementation. 
>
> On Aug 30, 2024, at 5:34 PM, rkerno <rik.ke...@projectcatalysts.com> 
> wrote:
>
> 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...@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
>  
> <https://groups.google.com/d/msgid/golang-nuts/8e726419-e984-4873-a2e9-d6a6eb4794a3n%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/6e453470-1d5e-47c1-97e7-67e7948d9c7fn%40googlegroups.com.

Reply via email to