github.com/april1989/origin-go-tools@v0.0.32/cmd/compilebench/main.go (about) 1 // Copyright 2015 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 5 // Compilebench benchmarks the speed of the Go compiler. 6 // 7 // Usage: 8 // 9 // compilebench [options] 10 // 11 // It times the compilation of various packages and prints results in 12 // the format used by package golibexec_testing (and expected by golang.org/x/perf/cmd/benchstat). 13 // 14 // The options are: 15 // 16 // -alloc 17 // Report allocations. 18 // 19 // -compile exe 20 // Use exe as the path to the cmd/compile binary. 21 // 22 // -compileflags 'list' 23 // Pass the space-separated list of flags to the compilation. 24 // 25 // -link exe 26 // Use exe as the path to the cmd/link binary. 27 // 28 // -linkflags 'list' 29 // Pass the space-separated list of flags to the linker. 30 // 31 // -count n 32 // Run each benchmark n times (default 1). 33 // 34 // -cpuprofile file 35 // Write a CPU profile of the compiler to file. 36 // 37 // -go path 38 // Path to "go" command (default "go"). 39 // 40 // -memprofile file 41 // Write a memory profile of the compiler to file. 42 // 43 // -memprofilerate rate 44 // Set runtime.MemProfileRate during compilation. 45 // 46 // -obj 47 // Report object file statistics. 48 // 49 // -pkg pkg 50 // Benchmark compiling a single package. 51 // 52 // -run regexp 53 // Only run benchmarks with names matching regexp. 54 // 55 // -short 56 // Skip long-running benchmarks. 57 // 58 // Although -cpuprofile and -memprofile are intended to write a 59 // combined profile for all the executed benchmarks to file, 60 // today they write only the profile for the last benchmark executed. 61 // 62 // The default memory profiling rate is one profile sample per 512 kB 63 // allocated (see ``go doc runtime.MemProfileRate''). 64 // Lowering the rate (for example, -memprofilerate 64000) produces 65 // a more fine-grained and therefore accurate profile, but it also incurs 66 // execution cost. For benchmark comparisons, never use timings 67 // obtained with a low -memprofilerate option. 68 // 69 // Example 70 // 71 // Assuming the base version of the compiler has been saved with 72 // ``toolstash save,'' this sequence compares the old and new compiler: 73 // 74 // compilebench -count 10 -compile $(toolstash -n compile) >old.txt 75 // compilebench -count 10 >new.txt 76 // benchstat old.txt new.txt 77 // 78 package main 79 80 import ( 81 "bytes" 82 "encoding/json" 83 "flag" 84 "fmt" 85 "io/ioutil" 86 "log" 87 "os" 88 "os/exec" 89 "path/filepath" 90 "regexp" 91 "strconv" 92 "strings" 93 "time" 94 ) 95 96 var ( 97 goroot string 98 compiler string 99 linker string 100 runRE *regexp.Regexp 101 is6g bool 102 ) 103 104 var ( 105 flagGoCmd = flag.String("go", "go", "path to \"go\" command") 106 flagAlloc = flag.Bool("alloc", false, "report allocations") 107 flagObj = flag.Bool("obj", false, "report object file stats") 108 flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary") 109 flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile") 110 flagLinker = flag.String("link", "", "use `exe` as the cmd/link binary") 111 flagLinkerFlags = flag.String("linkflags", "", "additional `flags` to pass to link") 112 flagRun = flag.String("run", "", "run benchmarks matching `regexp`") 113 flagCount = flag.Int("count", 1, "run benchmarks `n` times") 114 flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`") 115 flagMemprofile = flag.String("memprofile", "", "write memory profile to `file`") 116 flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`") 117 flagPackage = flag.String("pkg", "", "if set, benchmark the package at path `pkg`") 118 flagShort = flag.Bool("short", false, "skip long-running benchmarks") 119 ) 120 121 type test struct { 122 name string 123 r runner 124 } 125 126 type runner interface { 127 long() bool 128 run(name string, count int) error 129 } 130 131 var tests = []test{ 132 {"BenchmarkTemplate", compile{"html/template"}}, 133 {"BenchmarkUnicode", compile{"unicode"}}, 134 {"BenchmarkGoTypes", compile{"go/types"}}, 135 {"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}}, 136 {"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}}, 137 {"BenchmarkFlate", compile{"compress/flate"}}, 138 {"BenchmarkGoParser", compile{"go/parser"}}, 139 {"BenchmarkReflect", compile{"reflect"}}, 140 {"BenchmarkTar", compile{"archive/tar"}}, 141 {"BenchmarkXML", compile{"encoding/xml"}}, 142 {"BenchmarkLinkCompiler", link{"cmd/compile", ""}}, 143 {"BenchmarkExternalLinkCompiler", link{"cmd/compile", "-linkmode=external"}}, 144 {"BenchmarkLinkWithoutDebugCompiler", link{"cmd/compile", "-w"}}, 145 {"BenchmarkStdCmd", goBuild{[]string{"std", "cmd"}}}, 146 {"BenchmarkHelloSize", size{"$GOROOT/test/helloworld.go", false}}, 147 {"BenchmarkCmdGoSize", size{"cmd/go", true}}, 148 } 149 150 func usage() { 151 fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n") 152 fmt.Fprintf(os.Stderr, "options:\n") 153 flag.PrintDefaults() 154 os.Exit(2) 155 } 156 157 func main() { 158 log.SetFlags(0) 159 log.SetPrefix("compilebench: ") 160 flag.Usage = usage 161 flag.Parse() 162 if flag.NArg() != 0 { 163 usage() 164 } 165 166 s, err := exec.Command(*flagGoCmd, "env", "GOROOT").CombinedOutput() 167 if err != nil { 168 log.Fatalf("%s env GOROOT: %v", *flagGoCmd, err) 169 } 170 goroot = strings.TrimSpace(string(s)) 171 os.Setenv("GOROOT", goroot) // for any subcommands 172 173 compiler = *flagCompiler 174 if compiler == "" { 175 var foundTool string 176 foundTool, compiler = toolPath("compile", "6g") 177 if foundTool == "6g" { 178 is6g = true 179 } 180 } 181 182 linker = *flagLinker 183 if linker == "" && !is6g { // TODO: Support 6l 184 _, linker = toolPath("link") 185 } 186 187 if is6g { 188 *flagMemprofilerate = -1 189 *flagAlloc = false 190 *flagCpuprofile = "" 191 *flagMemprofile = "" 192 } 193 194 if *flagRun != "" { 195 r, err := regexp.Compile(*flagRun) 196 if err != nil { 197 log.Fatalf("invalid -run argument: %v", err) 198 } 199 runRE = r 200 } 201 202 if *flagPackage != "" { 203 tests = []test{ 204 {"BenchmarkPkg", compile{*flagPackage}}, 205 {"BenchmarkPkgLink", link{*flagPackage, ""}}, 206 } 207 runRE = nil 208 } 209 210 for i := 0; i < *flagCount; i++ { 211 for _, tt := range tests { 212 if tt.r.long() && *flagShort { 213 continue 214 } 215 if runRE == nil || runRE.MatchString(tt.name) { 216 if err := tt.r.run(tt.name, i); err != nil { 217 log.Printf("%s: %v", tt.name, err) 218 } 219 } 220 } 221 } 222 } 223 224 func toolPath(names ...string) (found, path string) { 225 var out1 []byte 226 var err1 error 227 for i, name := range names { 228 out, err := exec.Command(*flagGoCmd, "tool", "-n", name).CombinedOutput() 229 if err == nil { 230 return name, strings.TrimSpace(string(out)) 231 } 232 if i == 0 { 233 out1, err1 = out, err 234 } 235 } 236 log.Fatalf("go tool -n %s: %v\n%s", names[0], err1, out1) 237 return "", "" 238 } 239 240 type Pkg struct { 241 Dir string 242 GoFiles []string 243 } 244 245 func goList(dir string) (*Pkg, error) { 246 var pkg Pkg 247 out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output() 248 if err != nil { 249 return nil, fmt.Errorf("go list -json %s: %v", dir, err) 250 } 251 if err := json.Unmarshal(out, &pkg); err != nil { 252 return nil, fmt.Errorf("go list -json %s: unmarshal: %v", dir, err) 253 } 254 return &pkg, nil 255 } 256 257 func runCmd(name string, cmd *exec.Cmd) error { 258 start := time.Now() 259 out, err := cmd.CombinedOutput() 260 if err != nil { 261 return fmt.Errorf("%v\n%s", err, out) 262 } 263 fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds()) 264 return nil 265 } 266 267 type goBuild struct{ pkgs []string } 268 269 func (goBuild) long() bool { return true } 270 271 func (r goBuild) run(name string, count int) error { 272 args := []string{"build", "-a"} 273 if *flagCompilerFlags != "" { 274 args = append(args, "-gcflags", *flagCompilerFlags) 275 } 276 args = append(args, r.pkgs...) 277 cmd := exec.Command(*flagGoCmd, args...) 278 cmd.Dir = filepath.Join(goroot, "src") 279 return runCmd(name, cmd) 280 } 281 282 type size struct { 283 // path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go"). 284 path string 285 isLong bool 286 } 287 288 func (r size) long() bool { return r.isLong } 289 290 func (r size) run(name string, count int) error { 291 if strings.HasPrefix(r.path, "$GOROOT/") { 292 r.path = goroot + "/" + r.path[len("$GOROOT/"):] 293 } 294 295 cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", r.path) 296 cmd.Stdout = os.Stderr 297 cmd.Stderr = os.Stderr 298 if err := cmd.Run(); err != nil { 299 return err 300 } 301 defer os.Remove("_compilebenchout_") 302 info, err := os.Stat("_compilebenchout_") 303 if err != nil { 304 return err 305 } 306 out, err := exec.Command("size", "_compilebenchout_").CombinedOutput() 307 if err != nil { 308 return fmt.Errorf("size: %v\n%s", err, out) 309 } 310 lines := strings.Split(string(out), "\n") 311 if len(lines) < 2 { 312 return fmt.Errorf("not enough output from size: %s", out) 313 } 314 f := strings.Fields(lines[1]) 315 if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X 316 fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size()) 317 } else if strings.Contains(lines[0], "bss") && len(f) >= 3 { 318 fmt.Printf("%s 1 %s text-bytes %s data-bytes %s bss-bytes %v exe-bytes\n", name, f[0], f[1], f[2], info.Size()) 319 } 320 return nil 321 } 322 323 type compile struct{ dir string } 324 325 func (compile) long() bool { return false } 326 327 func (c compile) run(name string, count int) error { 328 // Make sure dependencies needed by go tool compile are installed to GOROOT/pkg. 329 out, err := exec.Command(*flagGoCmd, "build", "-i", c.dir).CombinedOutput() 330 if err != nil { 331 return fmt.Errorf("go build -i %s: %v\n%s", c.dir, err, out) 332 } 333 334 // Find dir and source file list. 335 pkg, err := goList(c.dir) 336 if err != nil { 337 return err 338 } 339 340 args := []string{"-o", "_compilebench_.o"} 341 args = append(args, strings.Fields(*flagCompilerFlags)...) 342 args = append(args, pkg.GoFiles...) 343 if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil { 344 return err 345 } 346 347 opath := pkg.Dir + "/_compilebench_.o" 348 if *flagObj { 349 // TODO(josharian): object files are big; just read enough to find what we seek. 350 data, err := ioutil.ReadFile(opath) 351 if err != nil { 352 log.Print(err) 353 } 354 // Find start of export data. 355 i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n") 356 // Count bytes to end of export data. 357 nexport := bytes.Index(data[i:], []byte("\n$$\n")) 358 fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport) 359 } 360 fmt.Println() 361 362 os.Remove(opath) 363 return nil 364 } 365 366 type link struct{ dir, flags string } 367 368 func (link) long() bool { return false } 369 370 func (r link) run(name string, count int) error { 371 if linker == "" { 372 // No linker. Skip the test. 373 return nil 374 } 375 376 // Build dependencies. 377 out, err := exec.Command(*flagGoCmd, "build", "-i", "-o", "/dev/null", r.dir).CombinedOutput() 378 if err != nil { 379 return fmt.Errorf("go build -i %s: %v\n%s", r.dir, err, out) 380 } 381 382 // Build the main package. 383 pkg, err := goList(r.dir) 384 if err != nil { 385 return err 386 } 387 args := []string{"-o", "_compilebench_.o"} 388 args = append(args, pkg.GoFiles...) 389 cmd := exec.Command(compiler, args...) 390 cmd.Dir = pkg.Dir 391 cmd.Stdout = os.Stderr 392 cmd.Stderr = os.Stderr 393 err = cmd.Run() 394 if err != nil { 395 return fmt.Errorf("compiling: %v", err) 396 } 397 defer os.Remove(pkg.Dir + "/_compilebench_.o") 398 399 // Link the main package. 400 args = []string{"-o", "_compilebench_.exe"} 401 args = append(args, strings.Fields(*flagLinkerFlags)...) 402 args = append(args, strings.Fields(r.flags)...) 403 args = append(args, "_compilebench_.o") 404 if err := runBuildCmd(name, count, pkg.Dir, linker, args); err != nil { 405 return err 406 } 407 fmt.Println() 408 defer os.Remove(pkg.Dir + "/_compilebench_.exe") 409 410 return err 411 } 412 413 // runBuildCmd runs "tool args..." in dir, measures standard build 414 // tool metrics, and prints a benchmark line. The caller may print 415 // additional metrics and then must print a newline. 416 // 417 // This assumes tool accepts standard build tool flags like 418 // -memprofilerate, -memprofile, and -cpuprofile. 419 func runBuildCmd(name string, count int, dir, tool string, args []string) error { 420 var preArgs []string 421 if *flagMemprofilerate >= 0 { 422 preArgs = append(preArgs, "-memprofilerate", fmt.Sprint(*flagMemprofilerate)) 423 } 424 if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" { 425 if *flagAlloc || *flagMemprofile != "" { 426 preArgs = append(preArgs, "-memprofile", "_compilebench_.memprof") 427 } 428 if *flagCpuprofile != "" { 429 preArgs = append(preArgs, "-cpuprofile", "_compilebench_.cpuprof") 430 } 431 } 432 cmd := exec.Command(tool, append(preArgs, args...)...) 433 cmd.Dir = dir 434 cmd.Stdout = os.Stderr 435 cmd.Stderr = os.Stderr 436 start := time.Now() 437 err := cmd.Run() 438 if err != nil { 439 return err 440 } 441 end := time.Now() 442 443 haveAllocs, haveRSS := false, false 444 var allocs, allocbytes, rssbytes int64 445 if *flagAlloc || *flagMemprofile != "" { 446 out, err := ioutil.ReadFile(dir + "/_compilebench_.memprof") 447 if err != nil { 448 log.Print("cannot find memory profile after compilation") 449 } 450 for _, line := range strings.Split(string(out), "\n") { 451 f := strings.Fields(line) 452 if len(f) < 4 || f[0] != "#" || f[2] != "=" { 453 continue 454 } 455 val, err := strconv.ParseInt(f[3], 0, 64) 456 if err != nil { 457 continue 458 } 459 haveAllocs = true 460 switch f[1] { 461 case "TotalAlloc": 462 allocbytes = val 463 case "Mallocs": 464 allocs = val 465 case "MaxRSS": 466 haveRSS = true 467 rssbytes = val 468 } 469 } 470 if !haveAllocs { 471 log.Println("missing stats in memprof (golang.org/issue/18641)") 472 } 473 474 if *flagMemprofile != "" { 475 outpath := *flagMemprofile 476 if *flagCount != 1 { 477 outpath = fmt.Sprintf("%s_%d", outpath, count) 478 } 479 if err := ioutil.WriteFile(outpath, out, 0666); err != nil { 480 log.Print(err) 481 } 482 } 483 os.Remove(dir + "/_compilebench_.memprof") 484 } 485 486 if *flagCpuprofile != "" { 487 out, err := ioutil.ReadFile(dir + "/_compilebench_.cpuprof") 488 if err != nil { 489 log.Print(err) 490 } 491 outpath := *flagCpuprofile 492 if *flagCount != 1 { 493 outpath = fmt.Sprintf("%s_%d", outpath, count) 494 } 495 if err := ioutil.WriteFile(outpath, out, 0666); err != nil { 496 log.Print(err) 497 } 498 os.Remove(dir + "/_compilebench_.cpuprof") 499 } 500 501 wallns := end.Sub(start).Nanoseconds() 502 userns := cmd.ProcessState.UserTime().Nanoseconds() 503 504 fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns) 505 if haveAllocs { 506 fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs) 507 } 508 if haveRSS { 509 fmt.Printf(" %d maxRSS/op", rssbytes) 510 } 511 512 return nil 513 }