Skip to content

Commit 851152f

Browse files
adonovangopherbot
authored andcommitted
go/ast/inspector: add explicit parent pointer
This CL optimizes the Cursor.Parent and Cursor.Stack operations by shrinking the event.index field to 32 bits (which is plenty) and using the liberated space (in push nodes) to hold an explicit parent pointer. This makes the CursorStack benchmark 4x faster, bringing it to parity with WithStack. The cost is 8% slower NewInspector. Before: BenchmarkNewInspector-8 3435 1695466 ns/op BenchmarkInspectCalls/WithStack-8 44512 140151 ns/op BenchmarkInspectCalls/CursorStack-8 10000 570207 ns/op After: BenchmarkNewInspector-8 3282 1836795 ns/op BenchmarkInspectCalls/WithStack-8 36932 154218 ns/op BenchmarkInspectCalls/CursorStack-8 37948 155916 ns/op Change-Id: I0607f5241de2db347cccb983e1ca4193572e6be1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/638336 Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent b75baa0 commit 851152f

File tree

4 files changed

+37
-66
lines changed

4 files changed

+37
-66
lines changed

go/ast/inspector/inspector.go

+20-13
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,10 @@ func New(files []*ast.File) *Inspector {
5656
// An event represents a push or a pop
5757
// of an ast.Node during a traversal.
5858
type event struct {
59-
node ast.Node
60-
typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
61-
index int // index of corresponding push or pop event
59+
node ast.Node
60+
typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
61+
index int32 // index of corresponding push or pop event
62+
parent int32 // index of parent's push node (defined for push nodes only)
6263
}
6364

6465
// TODO: Experiment with storing only the second word of event.node (unsafe.Pointer).
@@ -87,7 +88,7 @@ func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
8788
// })
8889

8990
mask := maskOf(types)
90-
for i := 0; i < len(in.events); {
91+
for i := int32(0); i < int32(len(in.events)); {
9192
ev := in.events[i]
9293
if ev.index > i {
9394
// push
@@ -117,7 +118,7 @@ func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
117118
// matches an element of the types slice.
118119
func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) {
119120
mask := maskOf(types)
120-
for i := 0; i < len(in.events); {
121+
for i := int32(0); i < int32(len(in.events)); {
121122
ev := in.events[i]
122123
if ev.index > i {
123124
// push
@@ -151,7 +152,7 @@ func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proc
151152
func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) {
152153
mask := maskOf(types)
153154
var stack []ast.Node
154-
for i := 0; i < len(in.events); {
155+
for i := int32(0); i < int32(len(in.events)); {
155156
ev := in.events[i]
156157
if ev.index > i {
157158
// push
@@ -200,18 +201,24 @@ func traverse(files []*ast.File) []event {
200201
events := make([]event, 0, capacity)
201202

202203
var stack []event
203-
stack = append(stack, event{}) // include an extra event so file nodes have a parent
204+
stack = append(stack, event{index: -1}) // include an extra event so file nodes have a parent
204205
for _, f := range files {
205206
ast.Inspect(f, func(n ast.Node) bool {
206207
if n != nil {
207208
// push
208209
ev := event{
209-
node: n,
210-
typ: 0, // temporarily used to accumulate type bits of subtree
211-
index: len(events), // push event temporarily holds own index
210+
node: n,
211+
typ: 0, // temporarily used to accumulate type bits of subtree
212+
index: int32(len(events)), // push event temporarily holds own index
213+
parent: stack[len(stack)-1].index,
212214
}
213215
stack = append(stack, ev)
214216
events = append(events, ev)
217+
218+
// 2B nodes ought to be enough for anyone!
219+
if int32(len(events)) < 0 {
220+
panic("event index exceeded int32")
221+
}
215222
} else {
216223
// pop
217224
top := len(stack) - 1
@@ -220,9 +227,9 @@ func traverse(files []*ast.File) []event {
220227
push := ev.index
221228
parent := top - 1
222229

223-
events[push].typ = typ // set type of push
224-
stack[parent].typ |= typ | ev.typ // parent's typ contains push and pop's typs.
225-
events[push].index = len(events) // make push refer to pop
230+
events[push].typ = typ // set type of push
231+
stack[parent].typ |= typ | ev.typ // parent's typ contains push and pop's typs.
232+
events[push].index = int32(len(events)) // make push refer to pop
226233

227234
stack = stack[:top]
228235
events = append(events, ev)

go/ast/inspector/iter.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (in *Inspector) PreorderSeq(types ...ast.Node) iter.Seq[ast.Node] {
2626

2727
return func(yield func(ast.Node) bool) {
2828
mask := maskOf(types)
29-
for i := 0; i < len(in.events); {
29+
for i := int32(0); i < int32(len(in.events)); {
3030
ev := in.events[i]
3131
if ev.index > i {
3232
// push
@@ -63,7 +63,7 @@ func All[N interface {
6363

6464
mask := typeOf((N)(nil))
6565
return func(yield func(N) bool) {
66-
for i := 0; i < len(in.events); {
66+
for i := int32(0); i < int32(len(in.events)); {
6767
ev := in.events[i]
6868
if ev.index > i {
6969
// push

internal/astutil/cursor/cursor.go

+11-48
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
// Call [Root] to obtain a valid cursor.
3232
type Cursor struct {
3333
in *inspector.Inspector
34-
index int // index of push node; -1 for virtual root node
34+
index int32 // index of push node; -1 for virtual root node
3535
}
3636

3737
// Root returns a cursor for the virtual root node,
@@ -63,9 +63,9 @@ func (c Cursor) String() string {
6363
}
6464

6565
// indices return the [start, end) half-open interval of event indices.
66-
func (c Cursor) indices() (int, int) {
66+
func (c Cursor) indices() (int32, int32) {
6767
if c.index < 0 {
68-
return 0, len(c.events()) // root: all events
68+
return 0, int32(len(c.events())) // root: all events
6969
} else {
7070
return c.index, c.events()[c.index].index + 1 // just one subtree
7171
}
@@ -154,10 +154,7 @@ func (c Cursor) Inspect(types []ast.Node, f func(c Cursor, push bool) (descend b
154154
// from the [ast.File] down to the current cursor's node.
155155
//
156156
// To amortize allocation, it appends to the provided slice, which
157-
// must be empty. Even so, a traversal that frequently calls Stack on
158-
// a Cursor may be slower than using [Inspector.WithStack].
159-
// For best performance, call Stack selectively, or use
160-
// [Cursor.Parent] instead where practical.
157+
// must be empty.
161158
//
162159
// Stack must not be called on the Root node.
163160
func (c Cursor) Stack(stack []Cursor) []Cursor {
@@ -168,30 +165,9 @@ func (c Cursor) Stack(stack []Cursor) []Cursor {
168165
panic("Cursor.Stack called on Root node")
169166
}
170167

171-
// The events[i] operation is a hotspot due to the bounds
172-
// check, unpredictable load, and high cache miss rate.
173-
// This could be mitigated two ways:
174-
//
175-
// 1. Reducing the size of the event struct by 25% by changing
176-
// event.node to just the value portion (unsafe.Pointer) of
177-
// the interface. (The ast.Node itab can be reconstructed
178-
// from the event.typ bitmask.) This was measured in CL 637740
179-
// but found to deliver disappointing gains (<10%).
180-
//
181-
// 2. Change event.index to uint32 (which is plenty). Though this
182-
// wouldn't change the size of the table, it would create
183-
// space for an explicit parent index, making Parent and
184-
// Stack optimal.
185-
186168
events := c.events()
187-
for i := c.index; i >= 0; i-- {
188-
if j := events[i].index; j > i {
189-
// push
190-
stack = append(stack, Cursor{c.in, i})
191-
} else {
192-
// pop: skip to before corresponding push
193-
i = j
194-
}
169+
for i := c.index; i >= 0; i = events[i].parent {
170+
stack = append(stack, Cursor{c.in, i})
195171
}
196172
slices.Reverse(stack)
197173
return stack
@@ -200,25 +176,12 @@ func (c Cursor) Stack(stack []Cursor) []Cursor {
200176
// Parent returns the parent of the current node.
201177
//
202178
// Parent must not be called on the Root node (whose [Cursor.Node] returns nil).
203-
//
204-
// The cost of this operation is proportional to the number of the
205-
// node's preceding siblings.
206179
func (c Cursor) Parent() Cursor {
207180
if c.index < 0 {
208181
panic("Cursor.Parent called on Root node")
209182
}
210183

211-
events := c.events()
212-
i := c.index - 1
213-
for i >= 0 {
214-
if j := events[i].index; j > i { // push?
215-
break
216-
} else {
217-
// pop: skip to before corresponding push
218-
i = j - 1
219-
}
220-
}
221-
return Cursor{c.in, i}
184+
return Cursor{c.in, c.events()[c.index].parent}
222185
}
223186

224187
// NextSibling returns the cursor for the next sibling node in the
@@ -234,7 +197,7 @@ func (c Cursor) NextSibling() (Cursor, bool) {
234197

235198
events := c.events()
236199
i := events[c.index].index + 1 // after corresponding pop
237-
if i < len(events) {
200+
if i < int32(len(events)) {
238201
if events[i].index > i { // push?
239202
return Cursor{c.in, i}, true
240203
}
@@ -267,8 +230,8 @@ func (c Cursor) PrevSibling() (Cursor, bool) {
267230
// or zero if it has no children.
268231
func (c Cursor) FirstChild() (Cursor, bool) {
269232
events := c.events()
270-
i := c.index + 1 // i=0 if c is root
271-
if i < len(events) && events[i].index > i { // push?
233+
i := c.index + 1 // i=0 if c is root
234+
if i < int32(len(events)) && events[i].index > i { // push?
272235
return Cursor{c.in, i}, true
273236
}
274237
return Cursor{}, false
@@ -359,7 +322,7 @@ func (c Cursor) FindPos(start, end token.Pos) (Cursor, bool) {
359322
// This algorithm could be implemented using c.Inspect,
360323
// but it is about 2.5x slower.
361324

362-
best := -1 // push index of latest (=innermost) node containing range
325+
best := int32(-1) // push index of latest (=innermost) node containing range
363326
for i, limit := c.indices(); i < limit; i++ {
364327
ev := events[i]
365328
if ev.index > i { // push?

internal/astutil/cursor/hooks.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import (
1818
// Copied from inspector.event; must remain in sync.
1919
// (Note that the linkname effects a type coercion too.)
2020
type event struct {
21-
node ast.Node
22-
typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
23-
index int // index of corresponding push or pop event (relative to this event's index, +ve=push, -ve=pop)
21+
node ast.Node
22+
typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
23+
index int32 // index of corresponding push or pop event (relative to this event's index, +ve=push, -ve=pop)
24+
parent int32 // index of parent's push node (defined for push nodes only)
2425
}
2526

2627
//go:linkname maskOf golang.org/x/tools/go/ast/inspector.maskOf

0 commit comments

Comments
 (0)