github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/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 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  //	-count n
    26  //		Run each benchmark n times (default 1).
    27  //
    28  //	-cpuprofile file
    29  //		Write a CPU profile of the compiler to file.
    30  //
    31  //	-memprofile file
    32  //		Write a memory profile of the compiler to file.
    33  //
    34  //	-memprofilerate rate
    35  //		Set runtime.MemProfileRate during compilation.
    36  //
    37  //	-obj
    38  //		Report object file statistics.
    39  //
    40  //  -pkg
    41  //		Benchmark compiling a single package.
    42  //
    43  //	-run regexp
    44  //		Only run benchmarks with names matching regexp.
    45  //
    46  // Although -cpuprofile and -memprofile are intended to write a
    47  // combined profile for all the executed benchmarks to file,
    48  // today they write only the profile for the last benchmark executed.
    49  //
    50  // The default memory profiling rate is one profile sample per 512 kB
    51  // allocated (see ``go doc runtime.MemProfileRate'').
    52  // Lowering the rate (for example, -memprofilerate 64000) produces
    53  // a more fine-grained and therefore accurate profile, but it also incurs
    54  // execution cost. For benchmark comparisons, never use timings
    55  // obtained with a low -memprofilerate option.
    56  //
    57  // Example
    58  //
    59  // Assuming the base version of the compiler has been saved with
    60  // ``toolstash save,'' this sequence compares the old and new compiler:
    61  //
    62  //	compilebench -count 10 -compile $(toolstash -n compile) >old.txt
    63  //	compilebench -count 10 >new.txt
    64  //	benchstat old.txt new.txt
    65  //
    66  package main
    67  
    68  import (
    69  	"bytes"
    70  	"flag"
    71  	"fmt"
    72  	"go/build"
    73  	"io/ioutil"
    74  	"log"
    75  	"os"
    76  	"os/exec"
    77  	"path/filepath"
    78  	"regexp"
    79  	"strconv"
    80  	"strings"
    81  	"time"
    82  )
    83  
    84  var (
    85  	goroot   string
    86  	compiler string
    87  	runRE    *regexp.Regexp
    88  	is6g     bool
    89  )
    90  
    91  var (
    92  	flagGoCmd          = flag.String("go", "go", "path to \"go\" command")
    93  	flagAlloc          = flag.Bool("alloc", false, "report allocations")
    94  	flagObj            = flag.Bool("obj", false, "report object file stats")
    95  	flagCompiler       = flag.String("compile", "", "use `exe` as the cmd/compile binary")
    96  	flagCompilerFlags  = flag.String("compileflags", "", "additional `flags` to pass to compile")
    97  	flagRun            = flag.String("run", "", "run benchmarks matching `regexp`")
    98  	flagCount          = flag.Int("count", 1, "run benchmarks `n` times")
    99  	flagCpuprofile     = flag.String("cpuprofile", "", "write CPU profile to `file`")
   100  	flagMemprofile     = flag.String("memprofile", "", "write memory profile to `file`")
   101  	flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`")
   102  	flagPackage        = flag.String("pkg", "", "if set, benchmark the package at path `pkg`")
   103  	flagShort          = flag.Bool("short", false, "skip long-running benchmarks")
   104  )
   105  
   106  var tests = []struct {
   107  	name string
   108  	dir  string
   109  	long bool
   110  }{
   111  	{"BenchmarkTemplate", "html/template", false},
   112  	{"BenchmarkUnicode", "unicode", false},
   113  	{"BenchmarkGoTypes", "go/types", false},
   114  	{"BenchmarkCompiler", "cmd/compile/internal/gc", false},
   115  	{"BenchmarkSSA", "cmd/compile/internal/ssa", false},
   116  	{"BenchmarkFlate", "compress/flate", false},
   117  	{"BenchmarkGoParser", "go/parser", false},
   118  	{"BenchmarkReflect", "reflect", false},
   119  	{"BenchmarkTar", "archive/tar", false},
   120  	{"BenchmarkXML", "encoding/xml", false},
   121  	{"BenchmarkStdCmd", "", true},
   122  	{"BenchmarkHelloSize", "", false},
   123  	{"BenchmarkCmdGoSize", "", true},
   124  }
   125  
   126  func usage() {
   127  	fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n")
   128  	fmt.Fprintf(os.Stderr, "options:\n")
   129  	flag.PrintDefaults()
   130  	os.Exit(2)
   131  }
   132  
   133  func main() {
   134  	log.SetFlags(0)
   135  	log.SetPrefix("compilebench: ")
   136  	flag.Usage = usage
   137  	flag.Parse()
   138  	if flag.NArg() != 0 {
   139  		usage()
   140  	}
   141  
   142  	s, err := exec.Command(*flagGoCmd, "env", "GOROOT").CombinedOutput()
   143  	if err != nil {
   144  		log.Fatalf("%s env GOROOT: %v", *flagGoCmd, err)
   145  	}
   146  	goroot = strings.TrimSpace(string(s))
   147  
   148  	compiler = *flagCompiler
   149  	if compiler == "" {
   150  		out, err := exec.Command(*flagGoCmd, "tool", "-n", "compile").CombinedOutput()
   151  		if err != nil {
   152  			out, err = exec.Command(*flagGoCmd, "tool", "-n", "6g").CombinedOutput()
   153  			is6g = true
   154  			if err != nil {
   155  				out, err = exec.Command(*flagGoCmd, "tool", "-n", "compile").CombinedOutput()
   156  				log.Fatalf("go tool -n compiler: %v\n%s", err, out)
   157  			}
   158  		}
   159  		compiler = strings.TrimSpace(string(out))
   160  	}
   161  
   162  	if *flagRun != "" {
   163  		r, err := regexp.Compile(*flagRun)
   164  		if err != nil {
   165  			log.Fatalf("invalid -run argument: %v", err)
   166  		}
   167  		runRE = r
   168  	}
   169  
   170  	for i := 0; i < *flagCount; i++ {
   171  		if *flagPackage != "" {
   172  			runBuild("BenchmarkPkg", *flagPackage, i)
   173  			continue
   174  		}
   175  		for _, tt := range tests {
   176  			if tt.long && *flagShort {
   177  				continue
   178  			}
   179  			if runRE == nil || runRE.MatchString(tt.name) {
   180  				runBuild(tt.name, tt.dir, i)
   181  			}
   182  		}
   183  	}
   184  }
   185  
   186  func runCmd(name string, cmd *exec.Cmd) {
   187  	start := time.Now()
   188  	out, err := cmd.CombinedOutput()
   189  	if err != nil {
   190  		log.Printf("%v: %v\n%s", name, err, out)
   191  		return
   192  	}
   193  	fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
   194  }
   195  
   196  func runStdCmd() {
   197  	args := []string{"build", "-a"}
   198  	if *flagCompilerFlags != "" {
   199  		args = append(args, "-gcflags", *flagCompilerFlags)
   200  	}
   201  	args = append(args, "std", "cmd")
   202  	cmd := exec.Command(*flagGoCmd, args...)
   203  	cmd.Dir = filepath.Join(goroot, "src")
   204  	runCmd("BenchmarkStdCmd", cmd)
   205  }
   206  
   207  // path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
   208  func runSize(name, path string) {
   209  	cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", path)
   210  	cmd.Stdout = os.Stderr
   211  	cmd.Stderr = os.Stderr
   212  	if err := cmd.Run(); err != nil {
   213  		log.Print(err)
   214  		return
   215  	}
   216  	defer os.Remove("_compilebenchout_")
   217  	info, err := os.Stat("_compilebenchout_")
   218  	if err != nil {
   219  		log.Print(err)
   220  		return
   221  	}
   222  	out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
   223  	if err != nil {
   224  		log.Printf("size: %v\n%s", err, out)
   225  		return
   226  	}
   227  	lines := strings.Split(string(out), "\n")
   228  	if len(lines) < 2 {
   229  		log.Printf("not enough output from size: %s", out)
   230  		return
   231  	}
   232  	f := strings.Fields(lines[1])
   233  	if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
   234  		fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size())
   235  	} else if strings.Contains(lines[0], "bss") && len(f) >= 3 {
   236  		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())
   237  	}
   238  }
   239  
   240  func runBuild(name, dir string, count int) {
   241  	switch name {
   242  	case "BenchmarkStdCmd":
   243  		runStdCmd()
   244  		return
   245  	case "BenchmarkCmdGoSize":
   246  		runSize("BenchmarkCmdGoSize", "cmd/go")
   247  		return
   248  	case "BenchmarkHelloSize":
   249  		runSize("BenchmarkHelloSize", filepath.Join(goroot, "test/helloworld.go"))
   250  		return
   251  	}
   252  
   253  	pkg, err := build.Import(dir, ".", 0)
   254  	if err != nil {
   255  		log.Print(err)
   256  		return
   257  	}
   258  	args := []string{"-o", "_compilebench_.o"}
   259  	if is6g {
   260  		*flagMemprofilerate = -1
   261  		*flagAlloc = false
   262  		*flagCpuprofile = ""
   263  		*flagMemprofile = ""
   264  	}
   265  	if *flagMemprofilerate >= 0 {
   266  		args = append(args, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
   267  	}
   268  	args = append(args, strings.Fields(*flagCompilerFlags)...)
   269  	if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
   270  		if *flagAlloc || *flagMemprofile != "" {
   271  			args = append(args, "-memprofile", "_compilebench_.memprof")
   272  		}
   273  		if *flagCpuprofile != "" {
   274  			args = append(args, "-cpuprofile", "_compilebench_.cpuprof")
   275  		}
   276  	}
   277  	args = append(args, pkg.GoFiles...)
   278  	cmd := exec.Command(compiler, args...)
   279  	cmd.Dir = pkg.Dir
   280  	cmd.Stdout = os.Stderr
   281  	cmd.Stderr = os.Stderr
   282  	start := time.Now()
   283  	err = cmd.Run()
   284  	if err != nil {
   285  		log.Printf("%v: %v", name, err)
   286  		return
   287  	}
   288  	end := time.Now()
   289  
   290  	var allocs, allocbytes int64
   291  	if *flagAlloc || *flagMemprofile != "" {
   292  		out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.memprof")
   293  		if err != nil {
   294  			log.Print("cannot find memory profile after compilation")
   295  		}
   296  		for _, line := range strings.Split(string(out), "\n") {
   297  			f := strings.Fields(line)
   298  			if len(f) < 4 || f[0] != "#" || f[2] != "=" {
   299  				continue
   300  			}
   301  			val, err := strconv.ParseInt(f[3], 0, 64)
   302  			if err != nil {
   303  				continue
   304  			}
   305  			switch f[1] {
   306  			case "TotalAlloc":
   307  				allocbytes = val
   308  			case "Mallocs":
   309  				allocs = val
   310  			}
   311  		}
   312  
   313  		if *flagMemprofile != "" {
   314  			if err := ioutil.WriteFile(*flagMemprofile, out, 0666); err != nil {
   315  				log.Print(err)
   316  			}
   317  		}
   318  		os.Remove(pkg.Dir + "/_compilebench_.memprof")
   319  	}
   320  
   321  	if *flagCpuprofile != "" {
   322  		out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.cpuprof")
   323  		if err != nil {
   324  			log.Print(err)
   325  		}
   326  		outpath := *flagCpuprofile
   327  		if *flagCount != 1 {
   328  			outpath = fmt.Sprintf("%s_%d", outpath, count)
   329  		}
   330  		if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
   331  			log.Print(err)
   332  		}
   333  		os.Remove(pkg.Dir + "/_compilebench_.cpuprof")
   334  	}
   335  
   336  	wallns := end.Sub(start).Nanoseconds()
   337  	userns := cmd.ProcessState.UserTime().Nanoseconds()
   338  
   339  	fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns)
   340  	if *flagAlloc {
   341  		fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs)
   342  	}
   343  
   344  	opath := pkg.Dir + "/_compilebench_.o"
   345  	if *flagObj {
   346  		// TODO(josharian): object files are big; just read enough to find what we seek.
   347  		data, err := ioutil.ReadFile(opath)
   348  		if err != nil {
   349  			log.Print(err)
   350  		}
   351  		// Find start of export data.
   352  		i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n")
   353  		// Count bytes to end of export data.
   354  		nexport := bytes.Index(data[i:], []byte("\n$$\n"))
   355  		fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport)
   356  	}
   357  	fmt.Println()
   358  
   359  	os.Remove(opath)
   360  }