Skip to content

Commit 04a9b16

Browse files
thepuddsrandall77
authored andcommitted
cmd/compile/internal/escape: avoid reading ir.Node during inner loop of walkOne
Broadly speaking, escape analysis has two main phases. First, it traverses the AST while building a data-flow graph of locations and edges. Second, during "solve", it repeatedly walks the data-flow graph while carefully propagating information about each location, including whether a location's address reaches the heap. Once escape analysis is in the solve phase and repeatedly walking the data-flow graph, almost all the information it needs is within the location graph, with a notable exception being the ir.Class of an ir.Name, which currently must be checked by following a pointer from the location to its ir.Node. For typical graphs, that does not matter much, but if the graph becomes large enough, cache misses in the inner solve loop start to matter more, and the class is checked many times in the inner loop. We therefore store the class information on the location in the graph to reduce how much memory we need to load in the inner loop. The package github.com/microsoft/typescript-go/internal/checker has many locations, and compilation currently spends most of its time in escape analysis. This CL gives roughly a 30% speedup for wall clock compilation time for the checker package: go1.24.0: 91.79s this CL: 64.98s Linux perf shows a healthy reduction for example in l2_request.miss and dTLB-load-misses on an amd64 test VM. We could tweak things a bit more, though initial review feedback has suggested it would be good to get this in as it stands. Subsequent CLs in this stack give larger improvements. Updates #72815 Change-Id: I3117430dff684c99e6da1e0d7763869873379238 Reviewed-on: https://go-review.googlesource.com/c/go/+/657295 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Jake Bailey <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent c0245b3 commit 04a9b16

File tree

2 files changed

+9
-2
lines changed

2 files changed

+9
-2
lines changed

src/cmd/compile/internal/escape/graph.go

+7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ type location struct {
7575
captured bool // has a closure captured this variable?
7676
reassigned bool // has this variable been reassigned?
7777
addrtaken bool // has this variable's address been taken?
78+
param bool // is this variable a parameter (ONAME of class ir.PPARAM)?
79+
paramOut bool // is this variable an out parameter (ONAME of class ir.PPARAMOUT)?
7880
}
7981

8082
type locAttr uint8
@@ -281,6 +283,11 @@ func (e *escape) newLoc(n ir.Node, persists bool) *location {
281283
curfn: e.curfn,
282284
loopDepth: e.loopDepth,
283285
}
286+
if loc.isName(ir.PPARAM) {
287+
loc.param = true
288+
} else if loc.isName(ir.PPARAMOUT) {
289+
loc.paramOut = true
290+
}
284291
if persists {
285292
loc.attrs |= attrPersists
286293
}

src/cmd/compile/internal/escape/solve.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func (b *batch) walkOne(root *location, walkgen uint32, enqueue func(*location))
126126
// corresponding result parameter, then record
127127
// that value flow for tagging the function
128128
// later.
129-
if l.isName(ir.PPARAM) {
129+
if l.param {
130130
if b.outlives(root, l) {
131131
if !l.hasAttr(attrEscapes) && (logopt.Enabled() || base.Flag.LowerM >= 2) {
132132
if base.Flag.LowerM >= 2 {
@@ -270,7 +270,7 @@ func (b *batch) outlives(l, other *location) bool {
270270
// We don't know what callers do with returned values, so
271271
// pessimistically we need to assume they flow to the heap and
272272
// outlive everything too.
273-
if l.isName(ir.PPARAMOUT) {
273+
if l.paramOut {
274274
// Exception: Closures can return locations allocated outside of
275275
// them without forcing them to the heap, if we can statically
276276
// identify all call sites. For example:

0 commit comments

Comments
 (0)