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 <[email protected]>
> 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 [email protected].
> 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 [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/8e726419-e984-4873-a2e9-d6a6eb4794a3n%40googlegroups.com.