Skip to content

Speeding up cgo builds

Peter Mattis edited this page Feb 11, 2016 · 2 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 (note the patch is against go1.6rc2, but it will likely patch cleanly against any go1.6 version):

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"
  • Home
  • Beta Dashboard
  • Release Process
  • [Building and running tests](Building and running tests)
    • [Using musl to get a completely static binary](Using musl to get a completely static binary)
  • Productivity
    • [Multiple GOPATHs](Multiple GOPATHs)
    • [Ben's Go Emacs setup](Ben's Go Emacs setup)
    • [Radu's vim setup](Radu's vim setup)
    • [Speeding up cgo builds](Speeding up cgo builds)
    • [Rando scripts](Rando scripts)
  • [Life of a transaction](Life of a transaction)
  • Readings
Clone this wiki locally