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.kernag...@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.
Hi Everyone,
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+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.
--
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/88032368-3399-4F26-858D-8A1C43DBDE5C%40ix.netcom.com.
|