cmd/compilebench: import from rsc.io
This CL is an import of compilebench from rsc.io/compilebench
at commit 1a5bec7724500bd6df47e053dbc42b41fe653372.
This CL brings in only main.go, unaltered.
The other files were:
* LICENSE: now unnecessary
* README.md: now unnecessary
* compilecmp: still available at its original home,
soon to be superceded by a new tool in x/tools
Once compilecmp is available in x/tools,
I will replace rsc.io/compilebench with a breadcrumb repo.
All outstanding pull requests against rsc.io/compilebench are from me.
They will be closed, and new CLs sent here as appropriate.
Change-Id: Ic436abf4a662173c6c184bc765b1b9cab13b3cfb
Reviewed-on: https://go-review.googlesource.com/39712
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/compilebench/main.go b/compilebench/main.go
new file mode 100644
index 0000000..9050ac1
--- /dev/null
+++ b/compilebench/main.go
@@ -0,0 +1,319 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Compilebench benchmarks the speed of the Go compiler.
+//
+// Usage:
+//
+// compilebench [options]
+//
+// It times the compilation of various packages and prints results in
+// the format used by package testing (and expected by rsc.io/benchstat).
+//
+// The options are:
+//
+// -alloc
+// Report allocations.
+//
+// -compile exe
+// Use exe as the path to the cmd/compile binary.
+//
+// -compileflags 'list'
+// Pass the space-separated list of flags to the compilation.
+//
+// -count n
+// Run each benchmark n times (default 1).
+//
+// -cpuprofile file
+// Write a CPU profile of the compiler to file.
+//
+// -memprofile file
+// Write a memory profile of the compiler to file.
+//
+// -memprofilerate rate
+// Set runtime.MemProfileRate during compilation.
+//
+// -run regexp
+// Only run benchmarks with names matching regexp.
+//
+// Although -cpuprofile and -memprofile are intended to write a
+// combined profile for all the executed benchmarks to file,
+// today they write only the profile for the last benchmark executed.
+//
+// The default memory profiling rate is one profile sample per 512 kB
+// allocated (see ``go doc runtime.MemProfileRate'').
+// Lowering the rate (for example, -memprofilerate 64000) produces
+// a more fine-grained and therefore accurate profile, but it also incurs
+// execution cost. For benchmark comparisons, never use timings
+// obtained with a low -memprofilerate option.
+//
+// Example
+//
+// Assuming the base version of the compiler has been saved with
+// ``toolstash save,'' this sequence compares the old and new compiler:
+//
+// compilebench -count 10 -compile $(toolstash -n compile) >old.txt
+// compilebench -count 10 >new.txt
+// benchstat old.txt new.txt
+//
+package main
+
+import (
+ "flag"
+ "fmt"
+ "go/build"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var (
+ goroot = runtime.GOROOT()
+ compiler string
+ runRE *regexp.Regexp
+ is6g bool
+)
+
+var (
+ flagAlloc = flag.Bool("alloc", false, "report allocations")
+ flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary")
+ flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile")
+ flagRun = flag.String("run", "", "run benchmarks matching `regexp`")
+ flagCount = flag.Int("count", 1, "run benchmarks `n` times")
+ flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`")
+ flagMemprofile = flag.String("memprofile", "", "write memory profile to `file`")
+ flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`")
+ flagShort = flag.Bool("short", false, "skip long-running benchmarks")
+)
+
+var tests = []struct {
+ name string
+ dir string
+ long bool
+}{
+ {"BenchmarkTemplate", "html/template", false},
+ {"BenchmarkUnicode", "unicode", false},
+ {"BenchmarkGoTypes", "go/types", false},
+ {"BenchmarkCompiler", "cmd/compile/internal/gc", false},
+ {"BenchmarkMakeBash", "", true},
+ {"BenchmarkHelloSize", "", false},
+ {"BenchmarkCmdGoSize", "", true},
+}
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n")
+ fmt.Fprintf(os.Stderr, "options:\n")
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+func main() {
+ log.SetFlags(0)
+ log.SetPrefix("compilebench: ")
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 0 {
+ usage()
+ }
+
+ compiler = *flagCompiler
+ if compiler == "" {
+ out, err := exec.Command("go", "tool", "-n", "compile").CombinedOutput()
+ if err != nil {
+ out, err = exec.Command("go", "tool", "-n", "6g").CombinedOutput()
+ is6g = true
+ if err != nil {
+ out, err = exec.Command("go", "tool", "-n", "compile").CombinedOutput()
+ log.Fatalf("go tool -n compiler: %v\n%s", err, out)
+ }
+ }
+ compiler = strings.TrimSpace(string(out))
+ }
+
+ if *flagRun != "" {
+ r, err := regexp.Compile(*flagRun)
+ if err != nil {
+ log.Fatalf("invalid -run argument: %v", err)
+ }
+ runRE = r
+ }
+
+ for i := 0; i < *flagCount; i++ {
+ for _, tt := range tests {
+ if tt.long && *flagShort {
+ continue
+ }
+ if runRE == nil || runRE.MatchString(tt.name) {
+ runBuild(tt.name, tt.dir)
+ }
+ }
+ }
+}
+
+func runCmd(name string, cmd *exec.Cmd) {
+ start := time.Now()
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Printf("%v: %v\n%s", name, err, out)
+ return
+ }
+ fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
+}
+
+func runMakeBash() {
+ cmd := exec.Command("./make.bash")
+ cmd.Dir = filepath.Join(runtime.GOROOT(), "src")
+ runCmd("BenchmarkMakeBash", cmd)
+}
+
+func runCmdGoSize() {
+ runSize("BenchmarkCmdGoSize", filepath.Join(runtime.GOROOT(), "bin/go"))
+}
+
+func runHelloSize() {
+ cmd := exec.Command("go", "build", "-o", "_hello_", filepath.Join(runtime.GOROOT(), "test/helloworld.go"))
+ cmd.Stdout = os.Stderr
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ log.Print(err)
+ return
+ }
+ defer os.Remove("_hello_")
+ runSize("BenchmarkHelloSize", "_hello_")
+}
+
+func runSize(name, file string) {
+ info, err := os.Stat(file)
+ if err != nil {
+ log.Print(err)
+ return
+ }
+ out, err := exec.Command("size", file).CombinedOutput()
+ if err != nil {
+ log.Printf("size: %v\n%s", err, out)
+ return
+ }
+ lines := strings.Split(string(out), "\n")
+ if len(lines) < 2 {
+ log.Printf("not enough output from size: %s", out)
+ return
+ }
+ f := strings.Fields(lines[1])
+ if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
+ fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size())
+ } else if strings.Contains(lines[0], "bss") && len(f) >= 3 {
+ 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())
+ }
+}
+
+func runBuild(name, dir string) {
+ switch name {
+ case "BenchmarkMakeBash":
+ runMakeBash()
+ return
+ case "BenchmarkCmdGoSize":
+ runCmdGoSize()
+ return
+ case "BenchmarkHelloSize":
+ runHelloSize()
+ return
+ }
+
+ pkg, err := build.Import(dir, ".", 0)
+ if err != nil {
+ log.Print(err)
+ return
+ }
+ args := []string{"-o", "_compilebench_.o"}
+ if is6g {
+ *flagMemprofilerate = -1
+ *flagAlloc = false
+ *flagCpuprofile = ""
+ *flagMemprofile = ""
+ }
+ if *flagMemprofilerate >= 0 {
+ args = append(args, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
+ }
+ args = append(args, strings.Fields(*flagCompilerFlags)...)
+ if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
+ if *flagAlloc || *flagMemprofile != "" {
+ args = append(args, "-memprofile", "_compilebench_.memprof")
+ }
+ if *flagCpuprofile != "" {
+ args = append(args, "-cpuprofile", "_compilebench_.cpuprof")
+ }
+ }
+ args = append(args, pkg.GoFiles...)
+ cmd := exec.Command(compiler, args...)
+ cmd.Dir = pkg.Dir
+ cmd.Stdout = os.Stderr
+ cmd.Stderr = os.Stderr
+ start := time.Now()
+ err = cmd.Run()
+ if err != nil {
+ log.Printf("%v: %v", name, err)
+ return
+ }
+ end := time.Now()
+
+ var allocs, bytes int64
+ if *flagAlloc || *flagMemprofile != "" {
+ out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.memprof")
+ if err != nil {
+ log.Print("cannot find memory profile after compilation")
+ }
+ for _, line := range strings.Split(string(out), "\n") {
+ f := strings.Fields(line)
+ if len(f) < 4 || f[0] != "#" || f[2] != "=" {
+ continue
+ }
+ val, err := strconv.ParseInt(f[3], 0, 64)
+ if err != nil {
+ continue
+ }
+ switch f[1] {
+ case "TotalAlloc":
+ bytes = val
+ case "Mallocs":
+ allocs = val
+ }
+ }
+
+ if *flagMemprofile != "" {
+ if err := ioutil.WriteFile(*flagMemprofile, out, 0666); err != nil {
+ log.Print(err)
+ }
+ }
+ os.Remove(pkg.Dir + "/_compilebench_.memprof")
+ }
+
+ if *flagCpuprofile != "" {
+ out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.cpuprof")
+ if err != nil {
+ log.Print(err)
+ }
+ if err := ioutil.WriteFile(*flagCpuprofile, out, 0666); err != nil {
+ log.Print(err)
+ }
+ os.Remove(pkg.Dir + "/_compilebench_.cpuprof")
+ }
+
+ wallns := end.Sub(start).Nanoseconds()
+ userns := cmd.ProcessState.UserTime().Nanoseconds()
+
+ if *flagAlloc {
+ fmt.Printf("%s 1 %d ns/op %d user-ns/op %d B/op %d allocs/op\n", name, wallns, userns, bytes, allocs)
+ } else {
+ fmt.Printf("%s 1 %d ns/op %d user-ns/op\n", name, wallns, userns)
+ }
+
+ os.Remove(pkg.Dir + "/_compilebench_.o")
+}