Speeding up cgo builds

Peter Mattis edited this page Feb 11, 2016 · 10 revisions

The go tool is used to build all of the CockroachDB source, including C and C++ dependencies via the cockroachdb/c-{rocksdb,snappy,protobuf} packages. Unfortunately, go build does not parallelize building of source files within a package. For .go files this isn't a problem because the Go compiler is fast. For C and C++ files the issue is more noticeable. Compiling the c-rocksdb package takes ~2min on my machine. Parallelizing the compilation of C and C++ files by go build reduces the build time to 30sec. If you frequently edit any of the cockroachdb/c-* packages and you're comfortable running a hacked version of the go tool, this patch is for you:

diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go
index a1f925e..8ca739c 100644
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -692,6 +692,8 @@ type builder struct {
        exec      sync.Mutex
        readySema chan bool
        ready     actionQueue
+
+       tasks chan func()
 }

 // An action represents a single action in the action graph.
@@ -1234,6 +1236,7 @@ func (b *builder) do(root *action) {
        }

        b.readySema = make(chan bool, len(all))
+       b.tasks = make(chan func(), buildP)

        // Initialize per-action execution state.
        for _, a := range all {
@@ -1310,6 +1313,8 @@ func (b *builder) do(root *action) {
                                        a := b.ready.pop()
                                        b.exec.Unlock()
                                        handle(a)
+                               case task := <-b.tasks:
+                                       task()
                                case <-interrupted:
                                        setExitStatus(1)
                                        return
@@ -3123,12 +3128,16 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, pcCFLAGS, pcLDFLAGS, cgofi
                staticLibs = []string{"-Wl,--start-group", "-lmingwex", "-lmingw32", "-Wl,--end-group"}
        }

+       var tasks []func()
+       var results chan error
+
        cflags := stringList(cgoCPPFLAGS, cgoCFLAGS)
        for _, cfile := range cfiles {
+               cfile := cfile
                ofile := obj + cfile[:len(cfile)-1] + "o"
-               if err := b.gcc(p, ofile, cflags, obj+cfile); err != nil {
-                       return nil, nil, err
-               }
+               tasks = append(tasks, func() {
+                       results <- b.gcc(p, ofile, cflags, obj+cfile)
+               })
                linkobj = append(linkobj, ofile)
                if !strings.HasSuffix(ofile, "_cgo_main.o") {
                        outObj = append(outObj, ofile)
@@ -3136,35 +3145,65 @@ func (b *builder) cgo(p *Package, cgoExe, obj string, pcCFLAGS, pcLDFLAGS, cgofi
        }

        for _, file := range gccfiles {
+               file := file
                ofile := obj + cgoRe.ReplaceAllString(file[:len(file)-1], "_") + "o"
-               if err := b.gcc(p, ofile, cflags, file); err != nil {
-                       return nil, nil, err
-               }
+               tasks = append(tasks, func() {
+                       results <- b.gcc(p, ofile, cflags, file)
+               })
                linkobj = append(linkobj, ofile)
                outObj = append(outObj, ofile)
        }

        cxxflags := stringList(cgoCPPFLAGS, cgoCXXFLAGS)
        for _, file := range gxxfiles {
+               file := file
                // Append .o to the file, just in case the pkg has file.c and file.cpp
                ofile := obj + cgoRe.ReplaceAllString(file, "_") + ".o"
-               if err := b.gxx(p, ofile, cxxflags, file); err != nil {
-                       return nil, nil, err
-               }
+               tasks = append(tasks, func() {
+                       results <- b.gxx(p, ofile, cxxflags, file)
+               })
                linkobj = append(linkobj, ofile)
                outObj = append(outObj, ofile)
        }

        for _, file := range mfiles {
+               file := file
                // Append .o to the file, just in case the pkg has file.c and file.m
                ofile := obj + cgoRe.ReplaceAllString(file, "_") + ".o"
-               if err := b.gcc(p, ofile, cflags, file); err != nil {
-                       return nil, nil, err
-               }
+               tasks = append(tasks, func() {
+                       results <- b.gcc(p, ofile, cflags, file)
+               })
                linkobj = append(linkobj, ofile)
                outObj = append(outObj, ofile)
        }

+       // Give the results channel enough capacity so that sending the
+       // result is guaranteed not to block.
+       results = make(chan error, len(tasks))
+
+       // Feed the tasks into the b.tasks channel on a separate goroutine
+       // because the b.tasks channel's limited capacity might cause
+       // sending the task to block.
+       go func() {
+               for _, task := range tasks {
+                       b.tasks <- task
+               }
+       }()
+
+       // Loop until we've received results from all of our tasks or an
+       // error occurs.
+       for count := 0; count < len(tasks); {
+               select {
+               case err := <-results:
+                       if err != nil {
+                               return nil, nil, err
+                       }
+                       count++
+               case task := <-b.tasks:
+                       task()
+               }
+       }
+
        linkobj = append(linkobj, p.SysoFiles...)
        dynobj := obj + "_cgo_.o"
        pie := (goarch == "arm" && goos == "linux") || goos == "android"
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.