-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/compile: declare and assign of function literal escapes to heap #70171
Comments
If you look at the output of |
I think this is probably the TODO here: go/src/cmd/compile/internal/ir/expr.go Line 930 in 989eed2
|
Probable the local variable is a trigger. There's no allocs reported after moving the And the original observation is from some real project code. I did not reproduced in pay ground yet after simplifying the code. but the allocs not happened after the local variable moved or removed. Senario 1: func NewNode() recycle.Recycler[producer.Node] {
root := recycle.Get[producer.Node, node]()
// i := 3 // the benchmark reports a alloc
root.Assign(func(bt producer.Node) {
i := 3 // the benchmark does not reports allocs after `i` moved here
newNode(bt.(*node), i)
})
return root
} Senario 2: func newNode(n *node, layer int) {
if layer <= 0 {
return
}
lRecycler := recycle.Get[producer.Node, node]()
rRecycler := recycle.Get[producer.Node, node]()
var lNode, rNode producer.Node // benchmark reports 2 allocs
lRecycler.Assign(func(bt producer.Node) {
bt.(*node).i = layer
n.left = bt
lNode = bt
})
rRecycler.Assign(func(bt producer.Node) {
bt.(*node).i = layer
n.right = bt
rNode = bt
})
newNode(lNode.(*node), layer-1)
newNode(rNode.(*node), layer-1)
} benchmark reports no allocs after removing the local variables func newNode(n *node, layer int) {
if layer <= 0 {
return
}
lRecycler := recycle.Get[producer.Node, node]()
rRecycler := recycle.Get[producer.Node, node]()
lRecycler.Assign(func(bt producer.Node) {
bt.(*node).i = layer
n.left = bt
newNode(bt.(*node), layer-1)
})
rRecycler.Assign(func(bt producer.Node) {
bt.(*node).i = layer
n.right = bt
newNode(bt.(*node), layer-1)
})
} the allocs increase accordingly after adding additional variables func newNode(n *node, layer int) {
if layer <= 0 {
return
}
lRecycler := recycle.Get[producer.Node, node]()
rRecycler := recycle.Get[producer.Node, node]()
var lNode, rNode producer.Node
var lNode2, rNode2 producer.Node
lRecycler.Assign(func(bt producer.Node) {
bt.(*node).i = layer + 1
n.left = bt
lNode = bt
lNode2 = bt
})
rRecycler.Assign(func(bt producer.Node) {
bt.(*node).i = layer + 1
n.right = bt
rNode = bt
rNode2 = bt
})
newNode(lNode.(*node), layer-1)
newNode(rNode.(*node), layer-1)
newNode(lNode2.(*node), layer-1)
newNode(rNode2.(*node), layer-1)
} |
I verified with ./consumer.go:25:6: newNode capturing by ref: lNode (addr=false assign=true width=16)
./consumer.go:25:6: lNode escapes to heap:
./consumer.go:25:6: flow: {storage for func literal} = &lNode:
./consumer.go:25:6: from lNode (captured by a closure) at ./consumer.go:30:3
./consumer.go:25:6: from lNode (reference) at ./consumer.go:30:3
./consumer.go:26:6: newNode capturing by ref: lNode2 (addr=false assign=true width=16)
./consumer.go:26:6: lNode2 escapes to heap:
./consumer.go:26:6: flow: {storage for func literal} = &lNode2:
./consumer.go:26:6: from lNode2 (captured by a closure) at ./consumer.go:31:3
./consumer.go:26:6: from lNode2 (reference) at ./consumer.go:31:3
./consumer.go:18:23: newNode capturing by value: layer (addr=false assign=false width=8)
./consumer.go:18:14: newNode capturing by value: n (addr=false assign=false width=8)
./consumer.go:25:13: newNode capturing by ref: rNode (addr=false assign=true width=16)
./consumer.go:25:13: rNode escapes to heap:
./consumer.go:25:13: flow: {storage for func literal} = &rNode:
./consumer.go:25:13: from rNode (captured by a closure) at ./consumer.go:36:3
./consumer.go:25:13: from rNode (reference) at ./consumer.go:36:3
./consumer.go:26:14: newNode capturing by ref: rNode2 (addr=false assign=true width=16)
./consumer.go:26:14: rNode2 escapes to heap:
./consumer.go:26:14: flow: {storage for func literal} = &rNode2:
./consumer.go:26:14: from rNode2 (captured by a closure) at ./consumer.go:37:3
./consumer.go:26:14: from rNode2 (reference) at ./consumer.go:37:3 |
cc @dr2chase for how we handle closures (with a declaration and then assignment, similar to the autotmp case). |
Go version
1.23.2
Output of
go env
in your module/workspace:What did you do?
when declare an closure and assign in separate lines the local variable escape:
but when declare the closure in one statement, it does not escape:
escape : https://go.dev/play/p/r36WtbRaJtf
not escape: https://go.dev/play/p/2u-ydl00_0i
What did you see happen?
The local variable i does not escape in one code block but escaped in another code block.
What did you expect to see?
The variable
i
should not escape in both code blocks.The text was updated successfully, but these errors were encountered: