From d0d0d9ebc2175ffb592761462bd3a5c2ceab354f Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Wed, 2 Oct 2024 13:27:13 +0000 Subject: [PATCH] gopls/internal/cache: memoize dependent hash on analysisNode Benchmarking demonstrated a nontrivial amount of time spent hashing dependency information in the computation of analysisNode.cacheKey. Memoize this hash to reduce cost. Change-Id: Ic123202fbdf00c9de7b3f697c40f593ef798cd42 Reviewed-on: https://go-review.googlesource.com/c/tools/+/617395 LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan --- gopls/internal/cache/analysis.go | 60 +++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/gopls/internal/cache/analysis.go b/gopls/internal/cache/analysis.go index 0d7fc46237d..9debc609048 100644 --- a/gopls/internal/cache/analysis.go +++ b/gopls/internal/cache/analysis.go @@ -539,6 +539,9 @@ type analysisNode struct { typesOnce sync.Once // guards lazy population of types and typesErr fields types *types.Package // type information lazily imported from summary typesErr error // an error producing type information + + depHashOnce sync.Once + _depHash file.Hash // memoized hash of data affecting dependents } func (an *analysisNode) String() string { return string(an.mp.ID) } @@ -597,6 +600,40 @@ func (an *analysisNode) _import() (*types.Package, error) { return an.types, an.typesErr } +// depHash computes the hash of node information that may affect other nodes +// depending on this node: the package path, export hash, and action results. +// +// The result is memoized to avoid redundant work when analysing multiple +// dependents. +func (an *analysisNode) depHash() file.Hash { + an.depHashOnce.Do(func() { + hasher := sha256.New() + fmt.Fprintf(hasher, "dep: %s\n", an.mp.PkgPath) + fmt.Fprintf(hasher, "export: %s\n", an.summary.DeepExportHash) + + // action results: errors and facts + actions := an.summary.Actions + names := make([]string, 0, len(actions)) + for name := range actions { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + summary := actions[name] + fmt.Fprintf(hasher, "action %s\n", name) + if summary.Err != "" { + fmt.Fprintf(hasher, "error %s\n", summary.Err) + } else { + fmt.Fprintf(hasher, "facts %s\n", summary.FactsHash) + // We can safely omit summary.diagnostics + // from the key since they have no downstream effect. + } + } + hasher.Sum(an._depHash[:0]) + }) + return an._depHash +} + // analyzeSummary is a gob-serializable summary of successfully // applying a list of analyzers to a package. type analyzeSummary struct { @@ -770,27 +807,8 @@ func (an *analysisNode) cacheKey() [sha256.Size]byte { // vdeps, in PackageID order for _, vdep := range moremaps.Sorted(an.succs) { - fmt.Fprintf(hasher, "dep: %s\n", vdep.mp.PkgPath) - fmt.Fprintf(hasher, "export: %s\n", vdep.summary.DeepExportHash) - - // action results: errors and facts - actions := vdep.summary.Actions - names := make([]string, 0, len(actions)) - for name := range actions { - names = append(names, name) - } - sort.Strings(names) - for _, name := range names { - summary := actions[name] - fmt.Fprintf(hasher, "action %s\n", name) - if summary.Err != "" { - fmt.Fprintf(hasher, "error %s\n", summary.Err) - } else { - fmt.Fprintf(hasher, "facts %s\n", summary.FactsHash) - // We can safely omit summary.diagnostics - // from the key since they have no downstream effect. - } - } + hash := vdep.depHash() + hasher.Write(hash[:]) } var hash [sha256.Size]byte