github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/callgraph/callgraph_test.go (about) 1 // 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. 4 package callgraph_test 5 6 import ( 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" 17 "golang.org/x/tools/go/pointer" 18 "golang.org/x/tools/go/ssa" 19 "golang.org/x/tools/go/ssa/ssautil" 20 ) 21 22 // Benchmarks comparing different callgraph algorithms implemented in 23 // x/tools/go/callgraph. Comparison is on both speed, memory and precision. 24 // Fewer edges and fewer reachable nodes implies a more precise result. 25 // Comparison is done on a hello world http server using net/http. 26 // 27 // Current results were on an i7 macbook on go version devel go1.20-2730. 28 // Number of nodes, edges, and reachable function are expected to vary between 29 // go versions. Timing results are expected to vary between machines. 30 // BenchmarkStatic-12 53 ms/op 6 MB/op 12113 nodes 37355 edges 1522 reachable 31 // BenchmarkCHA-12 86 ms/op 16 MB/op 12113 nodes 131717 edges 7640 reachable 32 // BenchmarkRTA-12 110 ms/op 12 MB/op 6566 nodes 42291 edges 5099 reachable 33 // BenchmarkPTA-12 1427 ms/op 600 MB/op 8714 nodes 28244 edges 4184 reachable 34 // BenchmarkVTA-12 600 ms/op 78 MB/op 12114 nodes 44861 edges 4919 reachable 35 // BenchmarkVTA2-12 793 ms/op 104 MB/op 5450 nodes 22208 edges 4042 reachable 36 // BenchmarkVTA3-12 977 ms/op 124 MB/op 4621 nodes 19331 edges 3700 reachable 37 // BenchmarkVTAAlt-12 372 ms/op 57 MB/op 7763 nodes 29912 edges 4258 reachable 38 // BenchmarkVTAAlt2-12 570 ms/op 78 MB/op 4838 nodes 20169 edges 3737 reachable 39 // 40 // Note: 41 // * Static is unsound and may miss real edges. 42 // * RTA starts from a main function and only includes reachable functions. 43 // * CHA starts from all functions. 44 // * VTA, VTA2, and VTA3 are starting from all functions and the CHA callgraph. 45 // VTA2 and VTA3 are the result of re-applying VTA to the functions reachable 46 // from main() via the callgraph of the previous stage. 47 // * VTAAlt, and VTAAlt2 start from the functions reachable from main via the 48 // CHA callgraph. 49 // * All algorithms are unsound w.r.t. reflection. 50 51 const httpEx = `package main 52 53 import ( 54 "fmt" 55 "net/http" 56 ) 57 58 func hello(w http.ResponseWriter, req *http.Request) { 59 fmt.Fprintf(w, "hello world\n") 60 } 61 62 func main() { 63 http.HandleFunc("/hello", hello) 64 http.ListenAndServe(":8090", nil) 65 } 66 ` 67 68 var ( 69 once sync.Once 70 prog *ssa.Program 71 main *ssa.Function 72 ) 73 74 func example() (*ssa.Program, *ssa.Function) { 75 once.Do(func() { 76 var conf loader.Config 77 f, err := conf.ParseFile("<input>", httpEx) 78 if err != nil { 79 log.Fatal(err) 80 } 81 conf.CreateFromFiles(f.Name.Name, f) 82 83 lprog, err := conf.Load() 84 if err != nil { 85 log.Fatalf("test 'package %s': Load: %s", f.Name.Name, err) 86 } 87 prog = ssautil.CreateProgram(lprog, ssa.InstantiateGenerics) 88 prog.Build() 89 90 main = prog.Package(lprog.Created[0].Pkg).Members["main"].(*ssa.Function) 91 }) 92 return prog, main 93 } 94 95 var stats bool = false // print stats? 96 97 func logStats(b *testing.B, cnd bool, name string, cg *callgraph.Graph, main *ssa.Function) { 98 if cnd && stats { 99 e := 0 100 for _, n := range cg.Nodes { 101 e += len(n.Out) 102 } 103 r := len(reaches(main, cg, false)) 104 b.Logf("%s:\t%d nodes\t%d edges\t%d reachable", name, len(cg.Nodes), e, r) 105 } 106 } 107 108 func BenchmarkStatic(b *testing.B) { 109 b.StopTimer() 110 prog, main := example() 111 b.StartTimer() 112 113 for i := 0; i < b.N; i++ { 114 cg := static.CallGraph(prog) 115 logStats(b, i == 0, "static", cg, main) 116 } 117 } 118 119 func BenchmarkCHA(b *testing.B) { 120 b.StopTimer() 121 prog, main := example() 122 b.StartTimer() 123 124 for i := 0; i < b.N; i++ { 125 cg := cha.CallGraph(prog) 126 logStats(b, i == 0, "cha", cg, main) 127 } 128 } 129 130 func BenchmarkRTA(b *testing.B) { 131 b.StopTimer() 132 _, main := example() 133 b.StartTimer() 134 135 for i := 0; i < b.N; i++ { 136 res := rta.Analyze([]*ssa.Function{main}, true) 137 cg := res.CallGraph 138 logStats(b, i == 0, "rta", cg, main) 139 } 140 } 141 142 func BenchmarkPTA(b *testing.B) { 143 b.StopTimer() 144 _, main := example() 145 b.StartTimer() 146 147 for i := 0; i < b.N; i++ { 148 config := &pointer.Config{Mains: []*ssa.Package{main.Pkg}, BuildCallGraph: true} 149 res, err := pointer.Analyze(config) 150 if err != nil { 151 b.Fatal(err) 152 } 153 logStats(b, i == 0, "pta", res.CallGraph, main) 154 } 155 } 156 157 func BenchmarkVTA(b *testing.B) { 158 b.StopTimer() 159 prog, main := example() 160 b.StartTimer() 161 162 for i := 0; i < b.N; i++ { 163 cg := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) 164 logStats(b, i == 0, "vta", cg, main) 165 } 166 } 167 168 func BenchmarkVTA2(b *testing.B) { 169 b.StopTimer() 170 prog, main := example() 171 b.StartTimer() 172 173 for i := 0; i < b.N; i++ { 174 vta1 := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) 175 cg := vta.CallGraph(reaches(main, vta1, true), vta1) 176 logStats(b, i == 0, "vta2", cg, main) 177 } 178 } 179 180 func BenchmarkVTA3(b *testing.B) { 181 b.StopTimer() 182 prog, main := example() 183 b.StartTimer() 184 185 for i := 0; i < b.N; i++ { 186 vta1 := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) 187 vta2 := vta.CallGraph(reaches(main, vta1, true), vta1) 188 cg := vta.CallGraph(reaches(main, vta2, true), vta2) 189 logStats(b, i == 0, "vta3", cg, main) 190 } 191 } 192 193 func BenchmarkVTAAlt(b *testing.B) { 194 b.StopTimer() 195 prog, main := example() 196 b.StartTimer() 197 198 for i := 0; i < b.N; i++ { 199 cha := cha.CallGraph(prog) 200 cg := vta.CallGraph(reaches(main, cha, true), cha) // start from only functions reachable by CHA. 201 logStats(b, i == 0, "vta-alt", cg, main) 202 } 203 } 204 205 func BenchmarkVTAAlt2(b *testing.B) { 206 b.StopTimer() 207 prog, main := example() 208 b.StartTimer() 209 210 for i := 0; i < b.N; i++ { 211 cha := cha.CallGraph(prog) 212 vta1 := vta.CallGraph(reaches(main, cha, true), cha) 213 cg := vta.CallGraph(reaches(main, vta1, true), vta1) 214 logStats(b, i == 0, "vta-alt2", cg, main) 215 } 216 } 217 218 // reaches computes the transitive closure of functions forward reachable 219 // via calls in cg starting from `sources`. If refs is true, include 220 // functions referred to in an instruction. 221 func reaches(source *ssa.Function, cg *callgraph.Graph, refs bool) map[*ssa.Function]bool { 222 seen := make(map[*ssa.Function]bool) 223 var visit func(f *ssa.Function) 224 visit = func(f *ssa.Function) { 225 if seen[f] { 226 return 227 } 228 seen[f] = true 229 230 if n := cg.Nodes[f]; n != nil { 231 for _, e := range n.Out { 232 if e.Site != nil { 233 visit(e.Callee.Func) 234 } 235 } 236 } 237 238 if refs { 239 var buf [10]*ssa.Value // avoid alloc in common case 240 for _, b := range f.Blocks { 241 for _, instr := range b.Instrs { 242 for _, op := range instr.Operands(buf[:0]) { 243 if fn, ok := (*op).(*ssa.Function); ok { 244 visit(fn) 245 } 246 } 247 } 248 } 249 } 250 } 251 visit(source) 252 return seen 253 }