]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add -p flag for parallelism (like make -j)
authorRuss Cox <rsc@golang.org>
Tue, 10 Jan 2012 05:06:31 +0000 (21:06 -0800)
committerRuss Cox <rsc@golang.org>
Tue, 10 Jan 2012 05:06:31 +0000 (21:06 -0800)
On my MacBookAir4,1:

19.94r   go install -a -p 1 std
12.36r   go install -a -p 2 std
9.76r   go install -a -p 3 std
10.77r   go install -a -p 4 std

86.57r   go test -p 1 std -short
52.69r   go test -p 2 std -short
43.75r   go test -p 3 std -short
40.44r   go test -p 4 std -short

157.50r   go test -p 1 std
99.58r   go test -p 2 std
87.24r   go test -p 3 std
80.18r   go test -p 4 std

R=golang-dev, adg, r
CC=golang-dev
https://golang.org/cl/5531057

src/cmd/go/build.go
src/cmd/go/run.go
src/cmd/go/test.go
src/cmd/go/testflag.go

index 1fc4a4273a586e4aaa87c41bf5021e0aba4dd55d..2abc944ef8691d999071c9b028863facd5868621 100644 (file)
@@ -21,14 +21,8 @@ import (
        "sync"
 )
 
-// Break init cycles
-func init() {
-       cmdBuild.Run = runBuild
-       cmdInstall.Run = runInstall
-}
-
 var cmdBuild = &Command{
-       UsageLine: "build [-a] [-n] [-v] [-x] [-o output] [importpath... | gofiles...]",
+       UsageLine: "build [-a] [-n] [-v] [-x] [-o output] [-p n] [importpath... | gofiles...]",
        Short:     "compile packages and dependencies",
        Long: `
 Build compiles the packages named by the import paths,
@@ -46,24 +40,49 @@ The -a flag forces rebuilding of packages that are already up-to-date.
 The -n flag prints the commands but does not run them.
 The -v flag prints the names of packages as they are compiled.
 The -x flag prints the commands.
+
 The -o flag specifies the output file name.
 It is an error to use -o when the command line specifies multiple packages.
 
+The -p flag specifies the number of builds that can be run in parallel.
+The default is the number of CPUs available.
+
 For more about import paths, see 'go help importpath'.
 
 See also: go install, go get, go clean.
        `,
 }
 
-var buildA = cmdBuild.Flag.Bool("a", false, "")
-var buildN = cmdBuild.Flag.Bool("n", false, "")
-var buildV = cmdBuild.Flag.Bool("v", false, "")
-var buildX = cmdBuild.Flag.Bool("x", false, "")
+func init() {
+       // break init cycle
+       cmdBuild.Run = runBuild
+       cmdInstall.Run = runInstall
+
+       addBuildFlags(cmdBuild)
+       addBuildFlags(cmdInstall)
+}
+
+// Flags set by multiple commands.
+var buildA bool               // -a flag
+var buildN bool               // -n flag
+var buildP = runtime.NumCPU() // -p flag
+var buildV bool               // -v flag
+var buildX bool               // -x flag
+
 var buildO = cmdBuild.Flag.String("o", "", "output file")
 
+// addBuildFlags adds the flags common to the build and install commands.
+func addBuildFlags(cmd *Command) {
+       cmd.Flag.BoolVar(&buildA, "a", false, "")
+       cmd.Flag.BoolVar(&buildN, "n", false, "")
+       cmd.Flag.IntVar(&buildP, "p", buildP, "")
+       cmd.Flag.BoolVar(&buildV, "v", false, "")
+       cmd.Flag.BoolVar(&buildX, "x", false, "")
+}
+
 func runBuild(cmd *Command, args []string) {
        var b builder
-       b.init(*buildA, *buildN, *buildV, *buildX)
+       b.init()
 
        var pkgs []*Package
        if len(args) > 0 && strings.HasSuffix(args[0], ".go") {
@@ -97,7 +116,7 @@ func runBuild(cmd *Command, args []string) {
 }
 
 var cmdInstall = &Command{
-       UsageLine: "install [-a] [-n] [-v] [-x] [importpath...]",
+       UsageLine: "install [-a] [-n] [-v] [-x] [-p n] [importpath...]",
        Short:     "compile and install packages and dependencies",
        Long: `
 Install compiles and installs the packages named by the import paths,
@@ -108,20 +127,18 @@ The -n flag prints the commands but does not run them.
 The -v flag prints the names of packages as they are compiled.
 The -x flag prints the commands.
 
+The -p flag specifies the number of builds that can be run in parallel.
+The default is the number of CPUs available.
+
 For more about import paths, see 'go help importpath'.
 
 See also: go build, go get, go clean.
        `,
 }
 
-var installA = cmdInstall.Flag.Bool("a", false, "")
-var installN = cmdInstall.Flag.Bool("n", false, "")
-var installV = cmdInstall.Flag.Bool("v", false, "")
-var installX = cmdInstall.Flag.Bool("x", false, "")
-
 func runInstall(cmd *Command, args []string) {
        var b builder
-       b.init(*installA, *installN, *installV, *installX)
+       b.init()
        a := &action{}
        for _, p := range packages(args) {
                a.deps = append(a.deps, b.action(modeInstall, modeInstall, p))
@@ -134,10 +151,6 @@ func runInstall(cmd *Command, args []string) {
 // build packages in parallel, and the builder will be shared.
 type builder struct {
        work        string               // the temporary work directory (ends in filepath.Separator)
-       aflag       bool                 // the -a flag
-       nflag       bool                 // the -n flag
-       vflag       bool                 // the -v flag
-       xflag       bool                 // the -x flag
        arch        string               // e.g., "6"
        goroot      string               // the $GOROOT
        goarch      string               // the $GOARCH
@@ -158,11 +171,12 @@ type builder struct {
 
 // An action represents a single action in the action graph.
 type action struct {
-       p        *Package  // the package this action works on
-       deps     []*action // actions that must happen before this one
-       triggers []*action // inverse of deps
-       cgo      *action   // action for cgo binary if needed
-       args     []string  // additional args for runProgram
+       p          *Package      // the package this action works on
+       deps       []*action     // actions that must happen before this one
+       triggers   []*action     // inverse of deps
+       cgo        *action       // action for cgo binary if needed
+       args       []string      // additional args for runProgram
+       testOutput *bytes.Buffer // test output buffer
 
        f          func(*builder, *action) error // the action itself (nil = no-op)
        ignoreFail bool                          // whether to run f even if dependencies fail
@@ -195,12 +209,8 @@ const (
        modeInstall
 )
 
-func (b *builder) init(aflag, nflag, vflag, xflag bool) {
+func (b *builder) init() {
        var err error
-       b.aflag = aflag
-       b.nflag = nflag
-       b.vflag = vflag
-       b.xflag = xflag
        b.actionCache = make(map[cacheKey]*action)
        b.mkdirCache = make(map[string]bool)
        b.goarch = build.DefaultContext.GOARCH
@@ -217,14 +227,14 @@ func (b *builder) init(aflag, nflag, vflag, xflag bool) {
                fatalf("%s", err)
        }
 
-       if nflag {
+       if buildN {
                b.work = "$WORK"
        } else {
                b.work, err = ioutil.TempDir("", "go-build")
                if err != nil {
                        fatalf("%s", err)
                }
-               if b.xflag {
+               if buildX {
                        fmt.Printf("WORK=%s\n", b.work)
                }
                atexit(func() { os.RemoveAll(b.work) })
@@ -312,7 +322,7 @@ func (b *builder) action(mode buildMode, depMode buildMode, p *Package) *action
                }
        }
 
-       if !p.Stale && !b.aflag && p.target != "" {
+       if !p.Stale && !buildA && p.target != "" {
                // p.Stale==false implies that p.target is up-to-date.
                // Record target name for use by actions depending on this one.
                a.target = p.target
@@ -434,8 +444,15 @@ func (b *builder) do(root *action) {
                }
        }
 
-       // TODO: Turn this knob for parallelism.
-       for i := 0; i < 1; i++ {
+       // Kick off goroutines according to parallelism.
+       // If we are using the -n flag (just printing commands)
+       // drop the parallelism to 1, both to make the output
+       // deterministic and because there is no real work anyway.
+       par := buildP
+       if buildN {
+               par = 1
+       }
+       for i := 0; i < par; i++ {
                go func() {
                        for _ = range b.readySema {
                                // Receiving a value from b.sema entitles
@@ -453,7 +470,7 @@ func (b *builder) do(root *action) {
 
 // build is the action for building a single package or command.
 func (b *builder) build(a *action) error {
-       if b.nflag {
+       if buildN {
                // In -n mode, print a banner between packages.
                // The banner is five lines so that when changes to
                // different sections of the bootstrap script have to
@@ -462,7 +479,7 @@ func (b *builder) build(a *action) error {
                fmt.Printf("\n#\n# %s\n#\n\n", a.p.ImportPath)
        }
 
-       if b.vflag {
+       if buildV {
                fmt.Fprintf(os.Stderr, "%s\n", a.p.ImportPath)
        }
 
@@ -671,9 +688,9 @@ func removeByRenaming(name string) error {
 
 // copyFile is like 'cp src dst'.
 func (b *builder) copyFile(dst, src string, perm uint32) error {
-       if b.nflag || b.xflag {
+       if buildN || buildX {
                b.showcmd("", "cp %s %s", src, dst)
-               if b.nflag {
+               if buildN {
                        return nil
                }
        }
@@ -792,9 +809,9 @@ var errPrintedOutput = errors.New("already printed output - no need to show erro
 // If the commnd fails, run prints information about the failure
 // and returns a non-nil error.
 func (b *builder) run(dir string, desc string, cmdline ...string) error {
-       if b.nflag || b.xflag {
+       if buildN || buildX {
                b.showcmd(dir, "%s", strings.Join(cmdline, " "))
-               if b.nflag {
+               if buildN {
                        return nil
                }
        }
@@ -831,9 +848,9 @@ func (b *builder) mkdir(dir string) error {
        }
        b.mkdirCache[dir] = true
 
-       if b.nflag || b.xflag {
+       if buildN || buildX {
                b.showcmd("", "mkdir -p %s", dir)
-               if b.nflag {
+               if buildN {
                        return nil
                }
        }
index 371ba165432d2a403ddb65054c3e0a580f285355..1582531faea66e9e1363b43b77f2b0beea22f018 100644 (file)
@@ -6,11 +6,6 @@ package main
 
 import ()
 
-// Break init loop.
-func init() {
-       cmdRun.Run = runRun
-}
-
 var cmdRun = &Command{
        UsageLine: "run [-a] [-n] [-x] gofiles... [-- arguments...]",
        Short:     "compile and run Go program",
@@ -25,13 +20,17 @@ See also: go build.
        `,
 }
 
-var runA = cmdRun.Flag.Bool("a", false, "")
-var runN = cmdRun.Flag.Bool("n", false, "")
-var runX = cmdRun.Flag.Bool("x", false, "")
+func init() {
+       cmdRun.Run = runRun // break init loop
+
+       cmdRun.Flag.BoolVar(&buildA, "a", false, "")
+       cmdRun.Flag.BoolVar(&buildN, "n", false, "")
+       cmdRun.Flag.BoolVar(&buildX, "x", false, "")
+}
 
 func runRun(cmd *Command, args []string) {
        var b builder
-       b.init(*runA, *runN, false, *runX)
+       b.init()
        files, args := splitArgs(args)
        p := goFilesPackage(files, "")
        p.target = "" // must build - not up to date
index e6b70dda4f86569f425bec1e63fb1c73272c9606..dd7ce46fa193a41d2eadfc1c4ac2431e7b461a7c 100644 (file)
@@ -17,6 +17,7 @@ import (
        "path/filepath"
        "strings"
        "text/template"
+       "time"
        "unicode"
        "unicode/utf8"
 )
@@ -28,7 +29,7 @@ func init() {
 
 var cmdTest = &Command{
        CustomFlags: true,
-       UsageLine:   "test [importpath...] [-file a.go -file b.go ...] [-c] [-x] [flags for test binary]",
+       UsageLine:   "test [-c] [-x] [-file a.go -file b.go ...] [-p n] [importpath...] [flags for test binary]",
        Short:       "test packages",
        Long: `
 'Go test' automates testing the packages named by the import paths.
@@ -54,8 +55,8 @@ compiled.)
 The package is built in a temporary directory so it does not interfere with the
 non-test installation.
 
-See 'go help testflag' for details about flags
-handled by 'go test' and the test binary.
+See 'go help testflag' for details about flags handled by 'go test'
+and the test binary.
 
 See 'go help importpath' for more about import paths.
 
@@ -78,6 +79,10 @@ The flags handled by 'go test' are:
            Use only the tests in the source file a.go.
            Multiple -file flags may be provided.
 
+       -p n
+           Compile and test up to n packages in parallel.
+           The default value is the number of CPUs available.
+
        -x  Print each subcommand gotest executes.
 
 The resulting test binary, called test.out, has its own flags:
@@ -194,11 +199,13 @@ See the documentation of the testing package for more information.
 
 var (
        testC        bool     // -c flag
+       testP        int      // -p flag
        testX        bool     // -x flag
        testV        bool     // -v flag
        testFiles    []string // -file flag(s)  TODO: not respected
        testArgs     []string
        testShowPass bool // whether to display passing output
+       testBench    bool
 )
 
 func runTest(cmd *Command, args []string) {
@@ -219,34 +226,52 @@ func runTest(cmd *Command, args []string) {
                fatalf("cannot use -c flag with multiple packages")
        }
 
+       buildX = testX
+       if testP > 0 {
+               buildP = testP
+       }
+
        var b builder
-       b.init(false, false, false, testX)
+       b.init()
 
-       var builds, runs []*action
+       var builds, runs, prints []*action
 
-       // Prepare build + run actions for all packages being tested.
+       // Prepare build + run + print actions for all packages being tested.
        for _, p := range pkgs {
-               buildTest, runTest, err := b.test(p)
+               buildTest, runTest, printTest, err := b.test(p)
                if err != nil {
                        errorf("%s", err)
                        continue
                }
                builds = append(builds, buildTest)
                runs = append(runs, runTest)
+               prints = append(prints, printTest)
        }
 
-       // Build+run the tests one at a time in the order
-       // specified on the command line.
-       // May want to revisit when we parallelize things,
-       // although probably not for benchmark runs.
-       for i, a := range builds {
+       // Ultimately the goal is to print the output.
+       root := &action{deps: prints}
+
+       // Force the printing of results to happen in order,
+       // one at a time.
+       for i, a := range prints {
                if i > 0 {
-                       // Make build of test i depend on
-                       // completing the run of test i-1.
-                       a.deps = append(a.deps, runs[i-1])
+                       a.deps = append(a.deps, prints[i-1])
+               }
+       }
+
+       // If we are benchmarking, force everything to
+       // happen in serial.  Could instead allow all the
+       // builds to run before any benchmarks start,
+       // but try this for now.
+       if testBench {
+               for i, a := range builds {
+                       if i > 0 {
+                               // Make build of test i depend on
+                               // completing the run of test i-1.
+                               a.deps = append(a.deps, runs[i-1])
+                       }
                }
        }
-       root := &action{deps: runs}
 
        // If we are building any out-of-date packages other
        // than those under test, warn.
@@ -273,11 +298,12 @@ func runTest(cmd *Command, args []string) {
        b.do(root)
 }
 
-func (b *builder) test(p *Package) (buildAction, runAction *action, err error) {
+func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, err error) {
        if len(p.info.TestGoFiles)+len(p.info.XTestGoFiles) == 0 {
                build := &action{p: p}
-               run := &action{f: (*builder).notest, p: p, deps: []*action{build}}
-               return build, run, nil
+               run := &action{p: p}
+               print := &action{f: (*builder).notest, p: p, deps: []*action{build}}
+               return build, run, print, nil
        }
 
        // Build Package structs describing:
@@ -294,7 +320,7 @@ func (b *builder) test(p *Package) (buildAction, runAction *action, err error) {
        for _, path := range p.info.TestImports {
                p1, err := loadPackage(path)
                if err != nil {
-                       return nil, nil, err
+                       return nil, nil, nil, err
                }
                imports = append(imports, p1)
        }
@@ -320,10 +346,10 @@ func (b *builder) test(p *Package) (buildAction, runAction *action, err error) {
        // Create the directory for the .a files.
        ptestDir, _ := filepath.Split(ptestObj)
        if err := b.mkdir(ptestDir); err != nil {
-               return nil, nil, err
+               return nil, nil, nil, err
        }
        if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), p); err != nil {
-               return nil, nil, err
+               return nil, nil, nil, err
        }
 
        // Test package.
@@ -395,6 +421,7 @@ func (b *builder) test(p *Package) (buildAction, runAction *action, err error) {
                        p:      pmain,
                        target: "test.out" + b.exe,
                }
+               printAction = &action{p: p, deps: []*action{runAction}} // nop
        } else {
                // run test
                runAction = &action{
@@ -403,9 +430,14 @@ func (b *builder) test(p *Package) (buildAction, runAction *action, err error) {
                        p:          p,
                        ignoreFail: true,
                }
+               printAction = &action{
+                       f:    (*builder).printTest,
+                       deps: []*action{runAction},
+                       p:    p,
+               }
        }
 
-       return pmainAction, runAction, nil
+       return pmainAction, runAction, printAction, nil
 }
 
 var pass = []byte("\nPASS\n")
@@ -414,10 +446,11 @@ var pass = []byte("\nPASS\n")
 func (b *builder) runTest(a *action) error {
        args := []string{a.deps[0].target}
        args = append(args, testArgs...)
+       a.testOutput = new(bytes.Buffer)
 
-       if b.nflag || b.xflag {
+       if buildN || buildX {
                b.showcmd("", "%s", strings.Join(args, " "))
-               if b.nflag {
+               if buildN {
                        return nil
                }
        }
@@ -425,33 +458,44 @@ func (b *builder) runTest(a *action) error {
        if a.failed {
                // We were unable to build the binary.
                a.failed = false
-               fmt.Printf("FAIL\t%s [build failed]\n", a.p.ImportPath)
+               fmt.Fprintf(a.testOutput, "FAIL\t%s [build failed]\n", a.p.ImportPath)
                exitStatus = 1
                return nil
        }
 
        cmd := exec.Command(args[0], args[1:]...)
        cmd.Dir = a.p.Dir
+       t0 := time.Now()
        out, err := cmd.CombinedOutput()
+       t1 := time.Now()
+       t := fmt.Sprintf("%.3fs", t1.Sub(t0).Seconds())
        if err == nil && (bytes.Equal(out, pass[1:]) || bytes.HasSuffix(out, pass)) {
-               fmt.Printf("ok  \t%s\n", a.p.ImportPath)
+               fmt.Fprintf(a.testOutput, "ok  \t%s\t%s\n", a.p.ImportPath, t)
                if testShowPass {
-                       os.Stdout.Write(out)
+                       a.testOutput.Write(out)
                }
                return nil
        }
 
-       fmt.Printf("FAIL\t%s\n", a.p.ImportPath)
+       fmt.Fprintf(a.testOutput, "FAIL\t%s\t%s\n", a.p.ImportPath, t)
        exitStatus = 1
        if len(out) > 0 {
-               os.Stdout.Write(out)
+               a.testOutput.Write(out)
                // assume printing the test binary's exit status is superfluous
        } else {
-               fmt.Printf("%s\n", err)
+               fmt.Fprintf(a.testOutput, "%s\n", err)
        }
        return nil
 }
 
+// printTest is the action for printing a test result.
+func (b *builder) printTest(a *action) error {
+       run := a.deps[0]
+       os.Stdout.Write(run.testOutput.Bytes())
+       run.testOutput = nil
+       return nil
+}
+
 // notest is the action for testing a package with no test files.
 func (b *builder) notest(a *action) error {
        fmt.Printf("?   \t%s [no test files]\n", a.p.ImportPath)
index 07133035e9a25d51cf6f0a59c3739716cfb285ca..a3cacd6574505b398babb3469caeeaf8b6304cd3 100644 (file)
@@ -20,6 +20,7 @@ var usageMessage = `Usage of go test:
   -c=false: compile but do not run the test binary
   -file=file_test.go: specify file to use for tests;
       use multiple times for multiple files
+  -p=n: build and test up to n packages in parallel
   -x=false: print command lines as they are executed
 
   // These flags can be passed with or without a "test." prefix: -v or -test.v.
@@ -57,6 +58,7 @@ var testFlagDefn = []*testFlagSpec{
        // local.
        {name: "c", isBool: true},
        {name: "file", multiOK: true},
+       {name: "p"},
        {name: "x", isBool: true},
 
        // passed to 6.out, adding a "test." prefix to the name if necessary: -v becomes -test.v.
@@ -117,12 +119,17 @@ func testFlags(args []string) (packageNames, passToTest []string) {
                switch f.name {
                case "c":
                        setBoolFlag(&testC, value)
+               case "p":
+                       setIntFlag(&testP, value)
                case "x":
                        setBoolFlag(&testX, value)
                case "v":
                        setBoolFlag(&testV, value)
                case "file":
                        testFiles = append(testFiles, value)
+               case "bench":
+                       // record that we saw the flag; don't care about the value
+                       testBench = true
                }
                if extraWord {
                        i++
@@ -196,3 +203,13 @@ func setBoolFlag(flag *bool, value string) {
        }
        *flag = x
 }
+
+// setIntFlag sets the addressed integer to the value.
+func setIntFlag(flag *int, value string) {
+       x, err := strconv.Atoi(value)
+       if err != nil {
+               fmt.Fprintf(os.Stderr, "go test: illegal int flag value %s\n", value)
+               usage()
+       }
+       *flag = x
+}