Skip to content

Commit 846bc6c

Browse files
committed
cmd/compile: change phi location to be optimistic at backedges
This is: (1) a simple trick that cuts the number of phi-nodes (temporarily) inserted into the ssa representation by a factor of 10, and can cut the user time to compile tricky inputs like gogo/protobuf tests from 13 user minutes to 9.5, and memory allocation from 3.4GB to 2.4GB. (2) a fix to sparse lookup, that does not rely on an assumption proven false by at least one pathological input "etldlen". These two changes fix unrelated compiler performance bugs, both necessary to obtain good performance compiling etldlen. Without them it takes 20 minutes or longer, with them it completes in 2 minutes, without a gigantic memory footprint. Updates #16407 Change-Id: Iaa8aaa8c706858b3d49de1c4865a7fd79e6f4ff7 Reviewed-on: https://go-review.googlesource.com/23136 Reviewed-by: Keith Randall <[email protected]> Run-TryBot: David Chase <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 305a0ac commit 846bc6c

File tree

2 files changed

+66
-43
lines changed

2 files changed

+66
-43
lines changed

src/cmd/compile/internal/gc/sparselocatephifunctions.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,13 @@ func (s *state) locatePotentialPhiFunctions(fn *Node) *sparseDefState {
153153
p := e.Block()
154154
dm.Use(t, p) // always count phi pred as "use"; no-op except for loop edges, which matter.
155155
x := t.stm.Find(p, ssa.AdjustAfter, helper) // Look for defs reaching or within predecessors.
156+
if x == nil { // nil def from a predecessor means a backedge that will be visited soon.
157+
continue
158+
}
156159
if defseen == nil {
157160
defseen = x
158161
}
159-
if defseen != x || x == nil { // TODO: too conservative at loops, does better if x == nil -> continue
162+
if defseen != x {
160163
// Need to insert a phi function here because predecessors's definitions differ.
161164
change = true
162165
// Phi insertion is at AdjustBefore, visible with find in same block at AdjustWithin or AdjustAfter.

src/cmd/compile/internal/ssa/sparsetreemap.go

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import "fmt"
1414
// the nearest tree ancestor of a given node such that the
1515
// ancestor is also in the set.
1616
//
17-
// Given a set of blocks {B1, B2, B3} within the dominator tree, established by
18-
// stm.Insert()ing B1, B2, B3, etc, a query at block B
17+
// Given a set of blocks {B1, B2, B3} within the dominator tree, established
18+
// by stm.Insert()ing B1, B2, B3, etc, a query at block B
1919
// (performed with stm.Find(stm, B, adjust, helper))
2020
// will return the member of the set that is the nearest strict
2121
// ancestor of B within the dominator tree, or nil if none exists.
@@ -49,9 +49,9 @@ type SparseTreeMap RBTint32
4949
// packages, such as gc.
5050
type SparseTreeHelper struct {
5151
Sdom []SparseTreeNode // indexed by block.ID
52-
Po []*Block // exported data
53-
Dom []*Block // exported data
54-
Ponums []int32 // exported data
52+
Po []*Block // exported data; the blocks, in a post-order
53+
Dom []*Block // exported data; the dominator of this block.
54+
Ponums []int32 // exported data; Po[Ponums[b.ID]] == b; the index of b in Po
5555
}
5656

5757
// NewSparseTreeHelper returns a SparseTreeHelper for use
@@ -79,11 +79,19 @@ func makeSparseTreeHelper(sdom SparseTree, dom, po []*Block, ponums []int32) *Sp
7979
// A sparseTreeMapEntry contains the data stored in a binary search
8080
// data structure indexed by (dominator tree walk) entry and exit numbers.
8181
// Each entry is added twice, once keyed by entry-1/entry/entry+1 and
82-
// once keyed by exit+1/exit/exit-1. (there are three choices of paired indices, not 9, and they properly nest)
82+
// once keyed by exit+1/exit/exit-1.
83+
//
84+
// Within a sparse tree, the two entries added bracket all their descendant
85+
// entries within the tree; the first insertion is keyed by entry number,
86+
// which comes before all the entry and exit numbers of descendants, and
87+
// the second insertion is keyed by exit number, which comes after all the
88+
// entry and exit numbers of the descendants.
8389
type sparseTreeMapEntry struct {
84-
index *SparseTreeNode
85-
block *Block // TODO: store this in a separate index.
86-
data interface{}
90+
index *SparseTreeNode // references the entry and exit numbers for a block in the sparse tree
91+
block *Block // TODO: store this in a separate index.
92+
data interface{}
93+
sparseParent *sparseTreeMapEntry // references the nearest ancestor of this block in the sparse tree.
94+
adjust int32 // at what adjustment was this node entered into the sparse tree? The same block may be entered more than once, but at different adjustments.
8795
}
8896

8997
// Insert creates a definition within b with data x.
@@ -98,12 +106,25 @@ func (m *SparseTreeMap) Insert(b *Block, adjust int32, x interface{}, helper *Sp
98106
// assert unreachable
99107
return
100108
}
101-
entry := &sparseTreeMapEntry{index: blockIndex, data: x}
109+
// sp will be the sparse parent in this sparse tree (nearest ancestor in the larger tree that is also in this sparse tree)
110+
sp := m.findEntry(b, adjust, helper)
111+
entry := &sparseTreeMapEntry{index: blockIndex, block: b, data: x, sparseParent: sp, adjust: adjust}
112+
102113
right := blockIndex.exit - adjust
103114
_ = rbtree.Insert(right, entry)
104115

105116
left := blockIndex.entry + adjust
106117
_ = rbtree.Insert(left, entry)
118+
119+
// This newly inserted block may now be the sparse parent of some existing nodes (the new sparse children of this block)
120+
// Iterate over nodes bracketed by this new node to correct their parent, but not over the proper sparse descendants of those nodes.
121+
_, d := rbtree.Lub(left) // Lub (not EQ) of left is either right or a sparse child
122+
for tme := d.(*sparseTreeMapEntry); tme != entry; tme = d.(*sparseTreeMapEntry) {
123+
tme.sparseParent = entry
124+
// all descendants of tme are unchanged;
125+
// next sparse sibling (or right-bracketing sparse parent == entry) is first node after tme.index.exit - tme.adjust
126+
_, d = rbtree.Lub(tme.index.exit - tme.adjust)
127+
}
107128
}
108129

109130
// Find returns the definition visible from block b, or nil if none can be found.
@@ -118,45 +139,41 @@ func (m *SparseTreeMap) Insert(b *Block, adjust int32, x interface{}, helper *Sp
118139
//
119140
// Another way to think of this is that Find searches for inputs, Insert defines outputs.
120141
func (m *SparseTreeMap) Find(b *Block, adjust int32, helper *SparseTreeHelper) interface{} {
142+
v := m.findEntry(b, adjust, helper)
143+
if v == nil {
144+
return nil
145+
}
146+
return v.data
147+
}
148+
149+
func (m *SparseTreeMap) findEntry(b *Block, adjust int32, helper *SparseTreeHelper) *sparseTreeMapEntry {
121150
rbtree := (*RBTint32)(m)
122151
if rbtree == nil {
123152
return nil
124153
}
125154
blockIndex := &helper.Sdom[b.ID]
155+
156+
// The Glb (not EQ) of this probe is either the entry-indexed end of a sparse parent
157+
// or the exit-indexed end of a sparse sibling
126158
_, v := rbtree.Glb(blockIndex.entry + adjust)
127-
for v != nil {
128-
otherEntry := v.(*sparseTreeMapEntry)
129-
otherIndex := otherEntry.index
130-
// Two cases -- either otherIndex brackets blockIndex,
131-
// or it doesn't.
132-
//
133-
// Note that if otherIndex and blockIndex are
134-
// the same block, then the glb test only passed
135-
// because the definition is "before",
136-
// i.e., k == blockIndex.entry-1
137-
// allowing equality is okay on the blocks check.
138-
if otherIndex.exit >= blockIndex.exit {
139-
// bracketed.
140-
return otherEntry.data
159+
160+
if v == nil {
161+
return nil
162+
}
163+
164+
otherEntry := v.(*sparseTreeMapEntry)
165+
if otherEntry.index.exit >= blockIndex.exit { // otherEntry exit after blockIndex exit; therefore, brackets
166+
return otherEntry
167+
}
168+
// otherEntry is a sparse Sibling, and shares the same sparse parent (nearest ancestor within larger tree)
169+
sp := otherEntry.sparseParent
170+
if sp != nil {
171+
if sp.index.exit < blockIndex.exit { // no ancestor found
172+
return nil
141173
}
142-
// In the not-bracketed case, we could memoize the results of
143-
// walking up the tree, but for now we won't.
144-
// Memoize plan is to take the gap (inclusive)
145-
// from otherIndex.exit+1 to blockIndex.entry-1
146-
// and insert it into this or a second tree.
147-
// Said tree would then need adjusting whenever
148-
// an insertion occurred.
149-
150-
// Expectation is that per-variable tree is sparse,
151-
// therefore probe siblings instead of climbing up.
152-
// Note that each sibling encountered in this walk
153-
// to find a defining ancestor shares that ancestor
154-
// because the walk skips over the interior -- each
155-
// Glb will be an exit, and the iteration is to the
156-
// Glb of the entry.
157-
_, v = rbtree.Glb(otherIndex.entry - 1)
174+
return sp
158175
}
159-
return nil // nothing found
176+
return nil
160177
}
161178

162179
func (m *SparseTreeMap) String() string {
@@ -165,5 +182,8 @@ func (m *SparseTreeMap) String() string {
165182
}
166183

167184
func (e *sparseTreeMapEntry) String() string {
168-
return fmt.Sprintf("index=%v, data=%v", e.index, e.data)
185+
if e == nil {
186+
return "nil"
187+
}
188+
return fmt.Sprintf("(index=%v, block=%v, data=%v)->%v", e.index, e.block, e.data, e.sparseParent)
169189
}

0 commit comments

Comments
 (0)