blob: aa8aca08597516b84e961050104374a98251dd23 [file] [log] [blame]
Tim Kinga4455fe2023-01-03 15:48:03 -08001// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4package callgraph_test
5
6import (
7 "log"
8 "sync"
9 "testing"
10
11 "golang.org/x/tools/go/callgraph"
12 "golang.org/x/tools/go/callgraph/cha"
13 "golang.org/x/tools/go/callgraph/rta"
14 "golang.org/x/tools/go/callgraph/static"
15 "golang.org/x/tools/go/callgraph/vta"
16 "golang.org/x/tools/go/loader"
Tim Kinga4455fe2023-01-03 15:48:03 -080017 "golang.org/x/tools/go/ssa"
18 "golang.org/x/tools/go/ssa/ssautil"
19)
20
21// Benchmarks comparing different callgraph algorithms implemented in
22// x/tools/go/callgraph. Comparison is on both speed, memory and precision.
23// Fewer edges and fewer reachable nodes implies a more precise result.
24// Comparison is done on a hello world http server using net/http.
25//
26// Current results were on an i7 macbook on go version devel go1.20-2730.
27// Number of nodes, edges, and reachable function are expected to vary between
28// go versions. Timing results are expected to vary between machines.
Tim Kinga4455fe2023-01-03 15:48:03 -080029// BenchmarkStatic-12 53 ms/op 6 MB/op 12113 nodes 37355 edges 1522 reachable
30// BenchmarkCHA-12 86 ms/op 16 MB/op 12113 nodes 131717 edges 7640 reachable
31// BenchmarkRTA-12 110 ms/op 12 MB/op 6566 nodes 42291 edges 5099 reachable
32// BenchmarkPTA-12 1427 ms/op 600 MB/op 8714 nodes 28244 edges 4184 reachable
Tim Kingfef5b762023-01-05 23:45:28 -080033// BenchmarkVTA-12 600 ms/op 78 MB/op 12114 nodes 44861 edges 4919 reachable
34// BenchmarkVTA2-12 793 ms/op 104 MB/op 5450 nodes 22208 edges 4042 reachable
35// BenchmarkVTA3-12 977 ms/op 124 MB/op 4621 nodes 19331 edges 3700 reachable
36// BenchmarkVTAAlt-12 372 ms/op 57 MB/op 7763 nodes 29912 edges 4258 reachable
37// BenchmarkVTAAlt2-12 570 ms/op 78 MB/op 4838 nodes 20169 edges 3737 reachable
Tim Kinga4455fe2023-01-03 15:48:03 -080038//
39// Note:
40// * Static is unsound and may miss real edges.
41// * RTA starts from a main function and only includes reachable functions.
42// * CHA starts from all functions.
43// * VTA, VTA2, and VTA3 are starting from all functions and the CHA callgraph.
44// VTA2 and VTA3 are the result of re-applying VTA to the functions reachable
45// from main() via the callgraph of the previous stage.
46// * VTAAlt, and VTAAlt2 start from the functions reachable from main via the
47// CHA callgraph.
48// * All algorithms are unsound w.r.t. reflection.
49
50const httpEx = `package main
51
52import (
53 "fmt"
54 "net/http"
55)
56
57func hello(w http.ResponseWriter, req *http.Request) {
58 fmt.Fprintf(w, "hello world\n")
59}
60
61func main() {
62 http.HandleFunc("/hello", hello)
63 http.ListenAndServe(":8090", nil)
64}
65`
66
67var (
68 once sync.Once
69 prog *ssa.Program
70 main *ssa.Function
71)
72
73func example() (*ssa.Program, *ssa.Function) {
74 once.Do(func() {
75 var conf loader.Config
76 f, err := conf.ParseFile("<input>", httpEx)
77 if err != nil {
78 log.Fatal(err)
79 }
80 conf.CreateFromFiles(f.Name.Name, f)
81
82 lprog, err := conf.Load()
83 if err != nil {
84 log.Fatalf("test 'package %s': Load: %s", f.Name.Name, err)
85 }
86 prog = ssautil.CreateProgram(lprog, ssa.InstantiateGenerics)
87 prog.Build()
88
89 main = prog.Package(lprog.Created[0].Pkg).Members["main"].(*ssa.Function)
90 })
91 return prog, main
92}
93
94var stats bool = false // print stats?
95
Tim Kingfef5b762023-01-05 23:45:28 -080096func logStats(b *testing.B, cnd bool, name string, cg *callgraph.Graph, main *ssa.Function) {
97 if cnd && stats {
Tim Kinga4455fe2023-01-03 15:48:03 -080098 e := 0
99 for _, n := range cg.Nodes {
100 e += len(n.Out)
101 }
Tim Kingfef5b762023-01-05 23:45:28 -0800102 r := len(reaches(main, cg, false))
Tim Kinga4455fe2023-01-03 15:48:03 -0800103 b.Logf("%s:\t%d nodes\t%d edges\t%d reachable", name, len(cg.Nodes), e, r)
104 }
105}
106
107func BenchmarkStatic(b *testing.B) {
108 b.StopTimer()
109 prog, main := example()
110 b.StartTimer()
111
112 for i := 0; i < b.N; i++ {
113 cg := static.CallGraph(prog)
Tim Kingfef5b762023-01-05 23:45:28 -0800114 logStats(b, i == 0, "static", cg, main)
Tim Kinga4455fe2023-01-03 15:48:03 -0800115 }
116}
117
118func BenchmarkCHA(b *testing.B) {
119 b.StopTimer()
120 prog, main := example()
121 b.StartTimer()
122
123 for i := 0; i < b.N; i++ {
124 cg := cha.CallGraph(prog)
Tim Kingfef5b762023-01-05 23:45:28 -0800125 logStats(b, i == 0, "cha", cg, main)
Tim Kinga4455fe2023-01-03 15:48:03 -0800126 }
127}
128
129func BenchmarkRTA(b *testing.B) {
130 b.StopTimer()
131 _, main := example()
132 b.StartTimer()
133
134 for i := 0; i < b.N; i++ {
135 res := rta.Analyze([]*ssa.Function{main}, true)
136 cg := res.CallGraph
Tim Kingfef5b762023-01-05 23:45:28 -0800137 logStats(b, i == 0, "rta", cg, main)
Tim Kinga4455fe2023-01-03 15:48:03 -0800138 }
139}
140
Tim Kinga4455fe2023-01-03 15:48:03 -0800141func BenchmarkVTA(b *testing.B) {
142 b.StopTimer()
143 prog, main := example()
144 b.StartTimer()
145
146 for i := 0; i < b.N; i++ {
147 cg := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
Tim Kingfef5b762023-01-05 23:45:28 -0800148 logStats(b, i == 0, "vta", cg, main)
Tim Kinga4455fe2023-01-03 15:48:03 -0800149 }
150}
151
152func BenchmarkVTA2(b *testing.B) {
153 b.StopTimer()
154 prog, main := example()
155 b.StartTimer()
156
157 for i := 0; i < b.N; i++ {
158 vta1 := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
Tim Kingfef5b762023-01-05 23:45:28 -0800159 cg := vta.CallGraph(reaches(main, vta1, true), vta1)
160 logStats(b, i == 0, "vta2", cg, main)
Tim Kinga4455fe2023-01-03 15:48:03 -0800161 }
162}
163
164func BenchmarkVTA3(b *testing.B) {
165 b.StopTimer()
166 prog, main := example()
167 b.StartTimer()
168
169 for i := 0; i < b.N; i++ {
170 vta1 := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
Tim Kingfef5b762023-01-05 23:45:28 -0800171 vta2 := vta.CallGraph(reaches(main, vta1, true), vta1)
172 cg := vta.CallGraph(reaches(main, vta2, true), vta2)
173 logStats(b, i == 0, "vta3", cg, main)
Tim Kinga4455fe2023-01-03 15:48:03 -0800174 }
175}
176
177func BenchmarkVTAAlt(b *testing.B) {
178 b.StopTimer()
179 prog, main := example()
180 b.StartTimer()
181
182 for i := 0; i < b.N; i++ {
183 cha := cha.CallGraph(prog)
Tim Kingfef5b762023-01-05 23:45:28 -0800184 cg := vta.CallGraph(reaches(main, cha, true), cha) // start from only functions reachable by CHA.
185 logStats(b, i == 0, "vta-alt", cg, main)
Tim Kinga4455fe2023-01-03 15:48:03 -0800186 }
187}
188
189func BenchmarkVTAAlt2(b *testing.B) {
190 b.StopTimer()
191 prog, main := example()
192 b.StartTimer()
193
194 for i := 0; i < b.N; i++ {
195 cha := cha.CallGraph(prog)
Tim Kingfef5b762023-01-05 23:45:28 -0800196 vta1 := vta.CallGraph(reaches(main, cha, true), cha)
197 cg := vta.CallGraph(reaches(main, vta1, true), vta1)
198 logStats(b, i == 0, "vta-alt2", cg, main)
Tim Kinga4455fe2023-01-03 15:48:03 -0800199 }
200}
201
Tim Kingfef5b762023-01-05 23:45:28 -0800202// reaches computes the transitive closure of functions forward reachable
203// via calls in cg starting from `sources`. If refs is true, include
204// functions referred to in an instruction.
205func reaches(source *ssa.Function, cg *callgraph.Graph, refs bool) map[*ssa.Function]bool {
Tim Kinga4455fe2023-01-03 15:48:03 -0800206 seen := make(map[*ssa.Function]bool)
Tim Kingfef5b762023-01-05 23:45:28 -0800207 var visit func(f *ssa.Function)
208 visit = func(f *ssa.Function) {
209 if seen[f] {
210 return
211 }
212 seen[f] = true
213
214 if n := cg.Nodes[f]; n != nil {
Tim Kinga4455fe2023-01-03 15:48:03 -0800215 for _, e := range n.Out {
Tim Kingfef5b762023-01-05 23:45:28 -0800216 if e.Site != nil {
217 visit(e.Callee.Func)
218 }
219 }
220 }
221
222 if refs {
223 var buf [10]*ssa.Value // avoid alloc in common case
224 for _, b := range f.Blocks {
225 for _, instr := range b.Instrs {
226 for _, op := range instr.Operands(buf[:0]) {
227 if fn, ok := (*op).(*ssa.Function); ok {
228 visit(fn)
229 }
230 }
231 }
Tim Kinga4455fe2023-01-03 15:48:03 -0800232 }
233 }
234 }
Tim Kingfef5b762023-01-05 23:45:28 -0800235 visit(source)
Tim Kinga4455fe2023-01-03 15:48:03 -0800236 return seen
237}