@mrjn found the allocation in x/serialize.go
concerning because some stack allocations might become heap allocations. Here is an example.
Old code:
func f() {
a := new(MyStruct)
// Do something to a. Doesn't pass it to anywhere.
}
The variable a
is going to be allocated on stack. But if you write
func f() {
a := NewMyStruct()
// Do something to a...
}
Then a
is going to be allocated on the heap. Initially, I wonder if Go would be smart enough. One thing to point out immediately: If the initialization is light, Go will inline NewMyStruct
and there’s no difference. Now, suppose there is no inlining. Let’s verify. Here’s the code.
package main
import (
"fmt"
)
type S struct{}
func getNew() *S {
return &S{}
}
func identity(x *S) *S { return x }
func main() {
a := getNew()
identity(a)
var b S
identity(&b)
c := new(S)
identity(c)
d := new(S)
fmt.Println(d)
var e S
fmt.Println(&e)
}
Then we run go run -gcflags '-l -m' main.go
. The -l
flag is to disable inlining of identity
function. The identity function is to use the variable without using any external function. Here is the output.
./main.go:10: &S literal escapes to heap
./main.go:13: leaking param: x to result ~r1 level=0
./main.go:26: d escapes to heap
./main.go:25: new(S) escapes to heap
./main.go:29: &e escapes to heap
./main.go:29: &e escapes to heap
./main.go:28: moved to heap: e
./main.go:20: main &b does not escape
./main.go:22: main new(S) does not escape
./main.go:26: main ... argument does not escape
./main.go:29: main ... argument does not escape
Indeed, NewStruct
will cause heap allocation (if not inlined).
Indeed, local variables (line 20) and new(MyStruct)
(line 22) will use the stack.
What I find intriguing is that the variables d
and e
both go to the heap. An innocent-looking fmt.Println
tells Go escape analyzer that d
and even e
can go elsewhere, and results in using the heap!