internal/lsp/cache: use metadataGraph.Clone in snapshot.clone

Rather than updating metadata directly in snapshot.clone, build a set of
updates to apply and call metadata.Clone.

After this change, metadata is only updated by cloning, so we can
eliminate some code that works with mutable metadata.

In the next CL we'll only update the metadata if something changed, but
this is intentionally left out of this CL to isolate the change.

Benchmark (didChange in kubernetes): ~55ms->65ms, because it is now more
work to compute uris.

For golang/go#45686

Change-Id: I048bed65760b266a209f67111c57fae29bd3e6f0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/340852
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Rob Findley 2021-08-09 10:39:21 -04:00 committed by Robert Findley
parent 8a9207816c
commit 39d3d49260
3 changed files with 25 additions and 52 deletions

View File

@ -30,14 +30,6 @@ type metadataGraph struct {
ids map[span.URI][]PackageID
}
func NewMetadataGraph() *metadataGraph {
return &metadataGraph{
metadata: make(map[PackageID]*KnownMetadata),
importedBy: make(map[PackageID][]PackageID),
ids: make(map[span.URI][]PackageID),
}
}
// Clone creates a new metadataGraph, applying the given updates to the
// receiver.
func (g *metadataGraph) Clone(updates map[PackageID]*KnownMetadata) *metadataGraph {

View File

@ -232,7 +232,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
initializeOnce: &sync.Once{},
generation: s.cache.store.Generation(generationName(v, 0)),
packages: make(map[packageKey]*packageHandle),
meta: NewMetadataGraph(),
meta: &metadataGraph{},
files: make(map[span.URI]source.VersionedFileHandle),
goFiles: newGoFileMap(),
symbols: make(map[span.URI]*symbolHandle),

View File

@ -705,25 +705,9 @@ func (s *snapshot) getModTidyHandle(uri span.URI) *modTidyHandle {
func (s *snapshot) getImportedBy(id PackageID) []PackageID {
s.mu.Lock()
defer s.mu.Unlock()
return s.getImportedByLocked(id)
}
func (s *snapshot) getImportedByLocked(id PackageID) []PackageID {
// If we haven't rebuilt the import graph since creating the snapshot.
if len(s.meta.importedBy) == 0 {
s.rebuildImportGraph()
}
return s.meta.importedBy[id]
}
func (s *snapshot) rebuildImportGraph() {
for id, m := range s.meta.metadata {
for _, importID := range m.Deps {
s.meta.importedBy[importID] = append(s.meta.importedBy[importID], id)
}
}
}
func (s *snapshot) addPackageHandle(ph *packageHandle) *packageHandle {
s.mu.Lock()
defer s.mu.Unlock()
@ -1705,7 +1689,6 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
builtin: s.builtin,
initializeOnce: s.initializeOnce,
initializedErr: s.initializedErr,
meta: NewMetadataGraph(),
packages: make(map[packageKey]*packageHandle, len(s.packages)),
actions: make(map[actionKey]*actionHandle, len(s.actions)),
files: make(map[span.URI]source.VersionedFileHandle, len(s.files)),
@ -1912,7 +1895,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
return
}
idsToInvalidate[id] = newInvalidateMetadata
for _, rid := range s.getImportedByLocked(id) {
for _, rid := range s.meta.importedBy[id] {
addRevDeps(rid, invalidateMetadata)
}
}
@ -1976,58 +1959,56 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
addForwardDeps(id)
}
// Copy the URI to package ID mappings, skipping only those URIs whose
// metadata will be reloaded in future calls to load.
// Compute which IDs are in the snapshot.
//
// TODO(rfindley): this step shouldn't be necessary, since we compute skipID
// above based on meta.ids.
deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged
idsInSnapshot := map[PackageID]bool{} // track all known IDs
for uri, ids := range s.meta.ids {
// Optimization: ids slices are typically numerous, short (<3),
// and rarely modified by this loop, so don't allocate copies
// until necessary.
var resultIDs []PackageID // nil implies equal to ids[:i:i]
for i, id := range ids {
for _, ids := range s.meta.ids {
for _, id := range ids {
if skipID[id] || deleteInvalidMetadata && idsToInvalidate[id] {
resultIDs = ids[:i:i] // unshare
continue
}
// The ID is not reachable from any workspace package, so it should
// be deleted.
if !reachableID[id] {
resultIDs = ids[:i:i] // unshare
continue
}
idsInSnapshot[id] = true
if resultIDs != nil {
resultIDs = append(resultIDs, id)
}
}
if resultIDs == nil {
resultIDs = ids
}
result.meta.ids[uri] = resultIDs
}
// TODO(adonovan): opt: represent PackageID as an index into a process-global
// dup-free list of all package names ever seen, then use a bitmap instead of
// a hash table for "PackageSet" (e.g. idsInSnapshot).
// Copy the package metadata. We only need to invalidate packages directly
// containing the affected file, and only if it changed in a relevant way.
// Compute which metadata updates are required. We only need to invalidate
// packages directly containing the affected file, and only if it changed in
// a relevant way.
metadataUpdates := make(map[PackageID]*KnownMetadata)
for k, v := range s.meta.metadata {
if !idsInSnapshot[k] {
// Delete metadata for IDs that are no longer reachable from files
// in the snapshot.
metadataUpdates[k] = nil
continue
}
invalidateMetadata := idsToInvalidate[k]
// Mark invalidated metadata rather than deleting it outright.
result.meta.metadata[k] = &KnownMetadata{
Metadata: v.Metadata,
Valid: v.Valid && !invalidateMetadata,
PkgFilesChanged: v.PkgFilesChanged || changedPkgFiles[k],
ShouldLoad: v.ShouldLoad || invalidateMetadata,
valid := v.Valid && !invalidateMetadata
pkgFilesChanged := v.PkgFilesChanged || changedPkgFiles[k]
shouldLoad := v.ShouldLoad || invalidateMetadata
if valid != v.Valid || pkgFilesChanged != v.PkgFilesChanged || shouldLoad != v.ShouldLoad {
// Mark invalidated metadata rather than deleting it outright.
metadataUpdates[k] = &KnownMetadata{
Metadata: v.Metadata,
Valid: valid,
PkgFilesChanged: pkgFilesChanged,
ShouldLoad: shouldLoad,
}
}
}
result.meta = s.meta.Clone(metadataUpdates)
result.workspacePackages = computeWorkspacePackages(result.meta)
// Inherit all of the go.mod-related handles.