Skip to content

Commit 446714a

Browse files
committed
btf: use iterators
Now that we're on 1.23 we can use iterators. The existing iterators don't really need to be modified. Allocations don't change, run time seems a little bit faster. core: 1 goos: linux goarch: amd64 pkg: github.com/cilium/ebpf/btf cpu: 13th Gen Intel(R) Core(TM) i7-1365U │ base.txt │ iter.txt │ │ sec/op │ sec/op vs base │ PostorderTraversal/single_type 49.79n ± ∞ ¹ 48.68n ± ∞ ¹ -2.23% (p=0.029 n=4) PostorderTraversal/cycle(1) 146.4n ± ∞ ¹ 135.8n ± ∞ ¹ -7.24% (p=0.029 n=4) PostorderTraversal/cycle(10) 1.928µ ± ∞ ¹ 1.861µ ± ∞ ¹ -3.45% (p=0.029 n=4) PostorderTraversal/gov_update_cpu_data 1.895m ± ∞ ¹ 1.907m ± ∞ ¹ ~ (p=0.057 n=4) geomean 2.271µ 2.201µ -3.12% ¹ need >= 6 samples for confidence interval at level 0.95 Signed-off-by: Lorenz Bauer <[email protected]>
1 parent 081fcff commit 446714a

File tree

6 files changed

+174
-153
lines changed

6 files changed

+174
-153
lines changed

btf/marshal.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,28 +243,27 @@ func (b *Builder) addString(str string) (uint32, error) {
243243
return b.strings.Add(str)
244244
}
245245

246-
func (e *encoder) allocateIDs(root Type) (err error) {
247-
visitInPostorder(root, e.visited, func(typ Type) bool {
246+
func (e *encoder) allocateIDs(root Type) error {
247+
for typ := range postorder(root, e.visited) {
248248
if _, ok := typ.(*Void); ok {
249-
return true
249+
continue
250250
}
251251

252252
if _, ok := e.ids[typ]; ok {
253-
return true
253+
continue
254254
}
255255

256256
id := e.lastID + 1
257257
if id < e.lastID {
258-
err = errors.New("type ID overflow")
259-
return false
258+
return errors.New("type ID overflow")
260259
}
261260

262261
e.pending.Push(typ)
263262
e.ids[typ] = id
264263
e.lastID = id
265-
return true
266-
})
267-
return
264+
}
265+
266+
return nil
268267
}
269268

270269
// id returns the ID for the given type or panics with an error.

btf/marshal_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ func TestRoundtripVMlinux(t *testing.T) {
8282
visited := make(map[Type]struct{})
8383
limitTypes:
8484
for i, typ := range types {
85-
visitInPostorder(typ, visited, func(_ Type) bool { return true })
85+
for range postorder(typ, visited) {
86+
}
8687
if len(visited) >= math.MaxInt16 {
8788
// IDs exceeding math.MaxUint16 can trigger a bug when loading BTF.
8889
// This can be removed once the patch lands.

btf/traversal.go

Lines changed: 120 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@ package btf
22

33
import (
44
"fmt"
5+
"iter"
56
)
67

78
// Functions to traverse a cyclic graph of types. The below was very useful:
89
// https://eli.thegreenplace.net/2015/directed-graph-traversal-orderings-and-applications-to-data-flow-analysis/#post-order-and-reverse-post-order
910

10-
// Visit all types reachable from root in postorder.
11-
//
12-
// Traversal stops if yield returns false.
13-
//
14-
// Returns false if traversal was aborted.
11+
// postorder yields all types reachable from root in post order.
12+
func postorder(root Type, visited map[Type]struct{}) iter.Seq[Type] {
13+
return func(yield func(Type) bool) {
14+
visitInPostorder(root, visited, yield)
15+
}
16+
}
17+
18+
// visitInPostorder is a separate function to avoid arguments escaping
19+
// to the heap. Don't change the setup without re-running the benchmarks.
1520
func visitInPostorder(root Type, visited map[Type]struct{}, yield func(typ Type) bool) bool {
1621
if _, ok := visited[root]; ok {
1722
return true
@@ -21,139 +26,134 @@ func visitInPostorder(root Type, visited map[Type]struct{}, yield func(typ Type)
2126
}
2227
visited[root] = struct{}{}
2328

24-
cont := children(root, func(child *Type) bool {
25-
return visitInPostorder(*child, visited, yield)
26-
})
27-
if !cont {
28-
return false
29+
for child := range children(root) {
30+
if !visitInPostorder(*child, visited, yield) {
31+
return false
32+
}
2933
}
3034

3135
return yield(root)
3236
}
3337

34-
// children calls yield on each child of typ.
35-
//
36-
// Traversal stops if yield returns false.
37-
//
38-
// Returns false if traversal was aborted.
39-
func children(typ Type, yield func(child *Type) bool) bool {
40-
// Explicitly type switch on the most common types to allow the inliner to
41-
// do its work. This avoids allocating intermediate slices from walk() on
42-
// the heap.
43-
var tags []string
44-
switch v := typ.(type) {
45-
case *Void, *Int, *Enum, *Fwd, *Float, *declTag:
46-
// No children to traverse.
47-
// declTags is declared as a leaf type since it's parsed into .Tags fields of other types
48-
// during unmarshaling.
49-
case *Pointer:
50-
if !yield(&v.Target) {
51-
return false
52-
}
53-
case *Array:
54-
if !yield(&v.Index) {
55-
return false
56-
}
57-
if !yield(&v.Type) {
58-
return false
59-
}
60-
case *Struct:
61-
for i := range v.Members {
62-
if !yield(&v.Members[i].Type) {
63-
return false
64-
}
65-
for _, t := range v.Members[i].Tags {
66-
var tag Type = &declTag{v, t, i}
67-
if !yield(&tag) {
68-
return false
69-
}
38+
// children yields all direct descendants of typ.
39+
func children(typ Type) iter.Seq[*Type] {
40+
return func(yield func(*Type) bool) {
41+
// Explicitly type switch on the most common types to allow the inliner to
42+
// do its work. This avoids allocating intermediate slices from walk() on
43+
// the heap.
44+
var tags []string
45+
switch v := typ.(type) {
46+
case *Void, *Int, *Enum, *Fwd, *Float, *declTag:
47+
// No children to traverse.
48+
// declTags is declared as a leaf type since it's parsed into .Tags fields of other types
49+
// during unmarshaling.
50+
case *Pointer:
51+
if !yield(&v.Target) {
52+
return
7053
}
71-
}
72-
tags = v.Tags
73-
case *Union:
74-
for i := range v.Members {
75-
if !yield(&v.Members[i].Type) {
76-
return false
77-
}
78-
for _, t := range v.Members[i].Tags {
79-
var tag Type = &declTag{v, t, i}
80-
if !yield(&tag) {
81-
return false
54+
case *Array:
55+
if !yield(&v.Index) {
56+
return
57+
}
58+
if !yield(&v.Type) {
59+
return
60+
}
61+
case *Struct:
62+
for i := range v.Members {
63+
if !yield(&v.Members[i].Type) {
64+
return
65+
}
66+
for _, t := range v.Members[i].Tags {
67+
var tag Type = &declTag{v, t, i}
68+
if !yield(&tag) {
69+
return
70+
}
8271
}
8372
}
84-
}
85-
tags = v.Tags
86-
case *Typedef:
87-
if !yield(&v.Type) {
88-
return false
89-
}
90-
tags = v.Tags
91-
case *Volatile:
92-
if !yield(&v.Type) {
93-
return false
94-
}
95-
case *Const:
96-
if !yield(&v.Type) {
97-
return false
98-
}
99-
case *Restrict:
100-
if !yield(&v.Type) {
101-
return false
102-
}
103-
case *Func:
104-
if !yield(&v.Type) {
105-
return false
106-
}
107-
if fp, ok := v.Type.(*FuncProto); ok {
108-
for i := range fp.Params {
109-
if len(v.ParamTags) <= i {
110-
continue
73+
tags = v.Tags
74+
case *Union:
75+
for i := range v.Members {
76+
if !yield(&v.Members[i].Type) {
77+
return
11178
}
112-
for _, t := range v.ParamTags[i] {
79+
for _, t := range v.Members[i].Tags {
11380
var tag Type = &declTag{v, t, i}
11481
if !yield(&tag) {
115-
return false
82+
return
11683
}
11784
}
11885
}
119-
}
120-
tags = v.Tags
121-
case *FuncProto:
122-
if !yield(&v.Return) {
123-
return false
124-
}
125-
for i := range v.Params {
126-
if !yield(&v.Params[i].Type) {
127-
return false
86+
tags = v.Tags
87+
case *Typedef:
88+
if !yield(&v.Type) {
89+
return
12890
}
129-
}
130-
case *Var:
131-
if !yield(&v.Type) {
132-
return false
133-
}
134-
tags = v.Tags
135-
case *Datasec:
136-
for i := range v.Vars {
137-
if !yield(&v.Vars[i].Type) {
138-
return false
91+
tags = v.Tags
92+
case *Volatile:
93+
if !yield(&v.Type) {
94+
return
13995
}
96+
case *Const:
97+
if !yield(&v.Type) {
98+
return
99+
}
100+
case *Restrict:
101+
if !yield(&v.Type) {
102+
return
103+
}
104+
case *Func:
105+
if !yield(&v.Type) {
106+
return
107+
}
108+
if fp, ok := v.Type.(*FuncProto); ok {
109+
for i := range fp.Params {
110+
if len(v.ParamTags) <= i {
111+
continue
112+
}
113+
for _, t := range v.ParamTags[i] {
114+
var tag Type = &declTag{v, t, i}
115+
if !yield(&tag) {
116+
return
117+
}
118+
}
119+
}
120+
}
121+
tags = v.Tags
122+
case *FuncProto:
123+
if !yield(&v.Return) {
124+
return
125+
}
126+
for i := range v.Params {
127+
if !yield(&v.Params[i].Type) {
128+
return
129+
}
130+
}
131+
case *Var:
132+
if !yield(&v.Type) {
133+
return
134+
}
135+
tags = v.Tags
136+
case *Datasec:
137+
for i := range v.Vars {
138+
if !yield(&v.Vars[i].Type) {
139+
return
140+
}
141+
}
142+
case *TypeTag:
143+
if !yield(&v.Type) {
144+
return
145+
}
146+
case *cycle:
147+
// cycle has children, but we ignore them deliberately.
148+
default:
149+
panic(fmt.Sprintf("don't know how to walk Type %T", v))
140150
}
141-
case *TypeTag:
142-
if !yield(&v.Type) {
143-
return false
144-
}
145-
case *cycle:
146-
// cycle has children, but we ignore them deliberately.
147-
default:
148-
panic(fmt.Sprintf("don't know how to walk Type %T", v))
149-
}
150151

151-
for _, t := range tags {
152-
var tag Type = &declTag{typ, t, -1}
153-
if !yield(&tag) {
154-
return false
152+
for _, t := range tags {
153+
var tag Type = &declTag{typ, t, -1}
154+
if !yield(&tag) {
155+
return
156+
}
155157
}
156158
}
157-
158-
return true
159159
}

0 commit comments

Comments
 (0)