cmd/go/internal/work: use par.Cache to cache tool IDs.

The tool IDs can be calculated once and reused across multiple
threads. This is a small optimization that helps optimize system
resources.

On a normal Windows machine with 12 virtual CPUs, the time to build
a hello world program is reduced from over 1 second, with spikes of 2
seconds, to a consistent 0.7 seconds.

Updates #71981.

Change-Id: I85f4a19f8ad4230afa32213780c761b7eb22fa29
Reviewed-on: https://go-review.googlesource.com/c/go/+/653715
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
qmuntal 2025-02-28 20:21:44 +01:00 committed by Quim Muntal
parent 039b3ebeba
commit f4750a6cfb
2 changed files with 42 additions and 52 deletions

View File

@ -10,6 +10,7 @@ import (
"bufio"
"bytes"
"cmd/internal/cov/covcmd"
"cmd/internal/par"
"container/heap"
"context"
"debug/elf"
@ -56,9 +57,10 @@ type Builder struct {
readySema chan bool
ready actionQueue
id sync.Mutex
toolIDCache map[string]string // tool name -> tool ID
buildIDCache map[string]string // file name -> build ID
id sync.Mutex
toolIDCache par.Cache[string, string] // tool name -> tool ID
gccToolIDCache map[string]string // tool name -> tool ID
buildIDCache map[string]string // file name -> build ID
}
// NOTE: Much of Action would not need to be exported if not for test.
@ -268,7 +270,7 @@ func NewBuilder(workDir string) *Builder {
b := new(Builder)
b.actionCache = make(map[cacheKey]*Action)
b.toolIDCache = make(map[string]string)
b.gccToolIDCache = make(map[string]string)
b.buildIDCache = make(map[string]string)
printWorkDir := false

View File

@ -144,55 +144,42 @@ func contentID(buildID string) string {
// build setups agree on details like $GOROOT and file name paths, but at least the
// tool IDs do not make it impossible.)
func (b *Builder) toolID(name string) string {
b.id.Lock()
id := b.toolIDCache[name]
b.id.Unlock()
return b.toolIDCache.Do(name, func() string {
path := base.Tool(name)
desc := "go tool " + name
if id != "" {
return id
}
path := base.Tool(name)
desc := "go tool " + name
// Special case: undocumented -vettool overrides usual vet,
// for testing vet or supplying an alternative analysis tool.
if name == "vet" && VetTool != "" {
path = VetTool
desc = VetTool
}
cmdline := str.StringList(cfg.BuildToolexec, path, "-V=full")
cmd := exec.Command(cmdline[0], cmdline[1:]...)
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
if stderr.Len() > 0 {
os.Stderr.WriteString(stderr.String())
// Special case: undocumented -vettool overrides usual vet,
// for testing vet or supplying an alternative analysis tool.
if name == "vet" && VetTool != "" {
path = VetTool
desc = VetTool
}
base.Fatalf("go: error obtaining buildID for %s: %v", desc, err)
}
line := stdout.String()
f := strings.Fields(line)
if len(f) < 3 || f[0] != name && path != VetTool || f[1] != "version" || f[2] == "devel" && !strings.HasPrefix(f[len(f)-1], "buildID=") {
base.Fatalf("go: parsing buildID from %s -V=full: unexpected output:\n\t%s", desc, line)
}
if f[2] == "devel" {
// On the development branch, use the content ID part of the build ID.
id = contentID(f[len(f)-1])
} else {
cmdline := str.StringList(cfg.BuildToolexec, path, "-V=full")
cmd := exec.Command(cmdline[0], cmdline[1:]...)
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
if stderr.Len() > 0 {
os.Stderr.WriteString(stderr.String())
}
base.Fatalf("go: error obtaining buildID for %s: %v", desc, err)
}
line := stdout.String()
f := strings.Fields(line)
if len(f) < 3 || f[0] != name && path != VetTool || f[1] != "version" || f[2] == "devel" && !strings.HasPrefix(f[len(f)-1], "buildID=") {
base.Fatalf("go: parsing buildID from %s -V=full: unexpected output:\n\t%s", desc, line)
}
if f[2] == "devel" {
// On the development branch, use the content ID part of the build ID.
return contentID(f[len(f)-1])
}
// For a release, the output is like: "compile version go1.9.1 X:framepointer".
// Use the whole line.
id = strings.TrimSpace(line)
}
b.id.Lock()
b.toolIDCache[name] = id
b.id.Unlock()
return id
return strings.TrimSpace(line)
})
}
// gccToolID returns the unique ID to use for a tool that is invoked
@ -216,10 +203,11 @@ func (b *Builder) toolID(name string) string {
// to detect changes in the underlying compiler. The returned exe can be empty,
// which means to rely only on the id.
func (b *Builder) gccToolID(name, language string) (id, exe string, err error) {
//TODO: Use par.Cache instead of a mutex and a map. See Builder.toolID.
key := name + "." + language
b.id.Lock()
id = b.toolIDCache[key]
exe = b.toolIDCache[key+".exe"]
id = b.gccToolIDCache[key]
exe = b.gccToolIDCache[key+".exe"]
b.id.Unlock()
if id != "" {
@ -309,8 +297,8 @@ func (b *Builder) gccToolID(name, language string) (id, exe string, err error) {
}
b.id.Lock()
b.toolIDCache[key] = id
b.toolIDCache[key+".exe"] = exe
b.gccToolIDCache[key] = id
b.gccToolIDCache[key+".exe"] = exe
b.id.Unlock()
return id, exe, nil