From f4750a6cfb8ae84acee56cca1a51c886fa16cc32 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 28 Feb 2025 20:21:44 +0100 Subject: [PATCH] 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 Reviewed-by: Ian Lance Taylor LUCI-TryBot-Result: Go LUCI --- src/cmd/go/internal/work/action.go | 10 ++-- src/cmd/go/internal/work/buildid.go | 84 +++++++++++++---------------- 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/src/cmd/go/internal/work/action.go b/src/cmd/go/internal/work/action.go index 44bb9f8c1e..2426720021 100644 --- a/src/cmd/go/internal/work/action.go +++ b/src/cmd/go/internal/work/action.go @@ -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 diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go index cab722c28a..3497ad7a27 100644 --- a/src/cmd/go/internal/work/buildid.go +++ b/src/cmd/go/internal/work/buildid.go @@ -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() - - 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 - } + return b.toolIDCache.Do(name, func() string { + 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()) + 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) } - 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 { + 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 -- 2.50.0