forked from joejulian/gio
layout: avoid heap escapes in Stack and Flex
Stack.Layout and Flex.Layout caused a lot of heap allocations / escapes. The reason was that scratch space for dims and call and was inside Stack/FlexChild. child.call.Add(gtx.Ops) confused the go escape analysis and caused the entired children slice to escape to the heap, including all widgets in it. This caused a lot of heap allocations. Now the scratch space is separate from children, and for cases len(children) <= 32, we will allocate the scratch space on the stack. For cases len(children) > 32, only the scratch space gets allocated from the heap, during append. Signed-off-by: vsariola <5684185+vsariola@users.noreply.github.com>
This commit is contained in:
parent
0a209f7d39
commit
1a17e9ea37
+24
-14
@@ -30,10 +30,6 @@ type FlexChild struct {
|
|||||||
weight float32
|
weight float32
|
||||||
|
|
||||||
widget Widget
|
widget Widget
|
||||||
|
|
||||||
// Scratch space.
|
|
||||||
call op.CallOp
|
|
||||||
dims Dimensions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spacing determine the spacing mode for a Flex.
|
// Spacing determine the spacing mode for a Flex.
|
||||||
@@ -88,6 +84,20 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
|||||||
remaining := mainMax
|
remaining := mainMax
|
||||||
var totalWeight float32
|
var totalWeight float32
|
||||||
cgtx := gtx
|
cgtx := gtx
|
||||||
|
// Note: previously the scratch space was inside FlexChild.
|
||||||
|
// child.call.Add(gtx.Ops) confused the go escape analysis and caused the
|
||||||
|
// entired children slice to be allocated on the heap, including all widgets
|
||||||
|
// in it. This produced a lot of object allocations. Now the scratch space
|
||||||
|
// is separate from children, and for cases len(children) <= 32, we will
|
||||||
|
// allocate the scratch space on the stack. For cases len(children) > 32,
|
||||||
|
// only the scratch space gets allocated from the heap, during append.
|
||||||
|
type scratchSpace struct {
|
||||||
|
call op.CallOp
|
||||||
|
dims Dimensions
|
||||||
|
}
|
||||||
|
var scratchArray [32]scratchSpace
|
||||||
|
scratch := scratchArray[:0]
|
||||||
|
scratch = append(scratch, make([]scratchSpace, len(children))...)
|
||||||
// Lay out Rigid children.
|
// Lay out Rigid children.
|
||||||
for i, child := range children {
|
for i, child := range children {
|
||||||
if child.flex {
|
if child.flex {
|
||||||
@@ -104,8 +114,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
|||||||
if remaining < 0 {
|
if remaining < 0 {
|
||||||
remaining = 0
|
remaining = 0
|
||||||
}
|
}
|
||||||
children[i].call = c
|
scratch[i].call = c
|
||||||
children[i].dims = dims
|
scratch[i].dims = dims
|
||||||
}
|
}
|
||||||
if w := f.WeightSum; w != 0 {
|
if w := f.WeightSum; w != 0 {
|
||||||
totalWeight = w
|
totalWeight = w
|
||||||
@@ -139,16 +149,16 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
|||||||
if remaining < 0 {
|
if remaining < 0 {
|
||||||
remaining = 0
|
remaining = 0
|
||||||
}
|
}
|
||||||
children[i].call = c
|
scratch[i].call = c
|
||||||
children[i].dims = dims
|
scratch[i].dims = dims
|
||||||
}
|
}
|
||||||
maxCross := crossMin
|
maxCross := crossMin
|
||||||
var maxBaseline int
|
var maxBaseline int
|
||||||
for _, child := range children {
|
for _, scratchChild := range scratch {
|
||||||
if c := f.Axis.Convert(child.dims.Size).Y; c > maxCross {
|
if c := f.Axis.Convert(scratchChild.dims.Size).Y; c > maxCross {
|
||||||
maxCross = c
|
maxCross = c
|
||||||
}
|
}
|
||||||
if b := child.dims.Size.Y - child.dims.Baseline; b > maxBaseline {
|
if b := scratchChild.dims.Size.Y - scratchChild.dims.Baseline; b > maxBaseline {
|
||||||
maxBaseline = b
|
maxBaseline = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,8 +179,8 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
|||||||
mainSize += space / (len(children) * 2)
|
mainSize += space / (len(children) * 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, child := range children {
|
for i, scratchChild := range scratch {
|
||||||
dims := child.dims
|
dims := scratchChild.dims
|
||||||
b := dims.Size.Y - dims.Baseline
|
b := dims.Size.Y - dims.Baseline
|
||||||
var cross int
|
var cross int
|
||||||
switch f.Alignment {
|
switch f.Alignment {
|
||||||
@@ -185,7 +195,7 @@ func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
|
|||||||
}
|
}
|
||||||
pt := f.Axis.Convert(image.Pt(mainSize, cross))
|
pt := f.Axis.Convert(image.Pt(mainSize, cross))
|
||||||
trans := op.Offset(pt).Push(gtx.Ops)
|
trans := op.Offset(pt).Push(gtx.Ops)
|
||||||
child.call.Add(gtx.Ops)
|
scratchChild.call.Add(gtx.Ops)
|
||||||
trans.Pop()
|
trans.Pop()
|
||||||
mainSize += f.Axis.Convert(dims.Size).X
|
mainSize += f.Axis.Convert(dims.Size).X
|
||||||
if i < len(children)-1 {
|
if i < len(children)-1 {
|
||||||
|
|||||||
+22
-12
@@ -20,10 +20,6 @@ type Stack struct {
|
|||||||
type StackChild struct {
|
type StackChild struct {
|
||||||
expanded bool
|
expanded bool
|
||||||
widget Widget
|
widget Widget
|
||||||
|
|
||||||
// Scratch space.
|
|
||||||
call op.CallOp
|
|
||||||
dims Dimensions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stacked returns a Stack child that is laid out with no minimum
|
// Stacked returns a Stack child that is laid out with no minimum
|
||||||
@@ -52,6 +48,20 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
|||||||
// First lay out Stacked children.
|
// First lay out Stacked children.
|
||||||
cgtx := gtx
|
cgtx := gtx
|
||||||
cgtx.Constraints.Min = image.Point{}
|
cgtx.Constraints.Min = image.Point{}
|
||||||
|
// Note: previously the scratch space was inside StackChild.
|
||||||
|
// child.call.Add(gtx.Ops) confused the go escape analysis and caused the
|
||||||
|
// entired children slice to be allocated on the heap, including all widgets
|
||||||
|
// in it. This produced a lot of object allocations. Now the scratch space
|
||||||
|
// is separate from children, and for cases len(children) <= 32, we will
|
||||||
|
// allocate the scratch space on the stack. For cases len(children) > 32,
|
||||||
|
// only the scratch space gets allocated from the heap, during append.
|
||||||
|
type scratchSpace struct {
|
||||||
|
call op.CallOp
|
||||||
|
dims Dimensions
|
||||||
|
}
|
||||||
|
var scratchArray [32]scratchSpace
|
||||||
|
scratch := scratchArray[:0]
|
||||||
|
scratch = append(scratch, make([]scratchSpace, len(children))...)
|
||||||
for i, w := range children {
|
for i, w := range children {
|
||||||
if w.expanded {
|
if w.expanded {
|
||||||
continue
|
continue
|
||||||
@@ -65,8 +75,8 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
|||||||
if h := dims.Size.Y; h > maxSZ.Y {
|
if h := dims.Size.Y; h > maxSZ.Y {
|
||||||
maxSZ.Y = h
|
maxSZ.Y = h
|
||||||
}
|
}
|
||||||
children[i].call = call
|
scratch[i].call = call
|
||||||
children[i].dims = dims
|
scratch[i].dims = dims
|
||||||
}
|
}
|
||||||
// Then lay out Expanded children.
|
// Then lay out Expanded children.
|
||||||
for i, w := range children {
|
for i, w := range children {
|
||||||
@@ -83,14 +93,14 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
|||||||
if h := dims.Size.Y; h > maxSZ.Y {
|
if h := dims.Size.Y; h > maxSZ.Y {
|
||||||
maxSZ.Y = h
|
maxSZ.Y = h
|
||||||
}
|
}
|
||||||
children[i].call = call
|
scratch[i].call = call
|
||||||
children[i].dims = dims
|
scratch[i].dims = dims
|
||||||
}
|
}
|
||||||
|
|
||||||
maxSZ = gtx.Constraints.Constrain(maxSZ)
|
maxSZ = gtx.Constraints.Constrain(maxSZ)
|
||||||
var baseline int
|
var baseline int
|
||||||
for _, ch := range children {
|
for _, scratchChild := range scratch {
|
||||||
sz := ch.dims.Size
|
sz := scratchChild.dims.Size
|
||||||
var p image.Point
|
var p image.Point
|
||||||
switch s.Alignment {
|
switch s.Alignment {
|
||||||
case N, S, Center:
|
case N, S, Center:
|
||||||
@@ -105,10 +115,10 @@ func (s Stack) Layout(gtx Context, children ...StackChild) Dimensions {
|
|||||||
p.Y = maxSZ.Y - sz.Y
|
p.Y = maxSZ.Y - sz.Y
|
||||||
}
|
}
|
||||||
trans := op.Offset(p).Push(gtx.Ops)
|
trans := op.Offset(p).Push(gtx.Ops)
|
||||||
ch.call.Add(gtx.Ops)
|
scratchChild.call.Add(gtx.Ops)
|
||||||
trans.Pop()
|
trans.Pop()
|
||||||
if baseline == 0 {
|
if baseline == 0 {
|
||||||
if b := ch.dims.Baseline; b != 0 {
|
if b := scratchChild.dims.Baseline; b != 0 {
|
||||||
baseline = b + maxSZ.Y - sz.Y - p.Y
|
baseline = b + maxSZ.Y - sz.Y - p.Y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user