internal/lsp/cache: cache isActiveLocked calculation across snapshots
This speeds up workspace initialization time in DegradeClosed memory
mode from 3m to 1m by avoiding unnecessary recomputation of results.
Change-Id: Ie5df82952d50ab42125defd148136329f0d50a48
Reviewed-on: https://go-review.googlesource.com/c/tools/+/413658
Reviewed-by: Alan Donovan <adonovan@google.com>
Reviewed-by: David Chase <drchase@google.com>
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index 5ded278..7ebc777 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -224,7 +224,7 @@
if s.view.Options().MemoryMode == source.ModeNormal {
return source.ParseFull
}
- if s.isActiveLocked(id, nil) {
+ if s.isActiveLocked(id) {
return source.ParseFull
}
return source.ParseExported
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 00d4ab2..e4fd671 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -220,7 +220,7 @@
// invalidate the reverse transitive closure of packages that have changed.
invalidatedPackages := s.meta.reverseTransitiveClosure(true, loadedIDs...)
s.meta = s.meta.Clone(updates)
-
+ s.resetIsActivePackageLocked()
// Invalidate any packages we may have associated with this metadata.
//
// TODO(rfindley): this should not be necessary, as we should have already
diff --git a/internal/lsp/cache/maps.go b/internal/lsp/cache/maps.go
index f2958b0..af04177 100644
--- a/internal/lsp/cache/maps.go
+++ b/internal/lsp/cache/maps.go
@@ -112,6 +112,40 @@
m.impl.Delete(key)
}
+type isActivePackageCacheMap struct {
+ impl *persistent.Map
+}
+
+func newIsActivePackageCacheMap() isActivePackageCacheMap {
+ return isActivePackageCacheMap{
+ impl: persistent.NewMap(func(a, b interface{}) bool {
+ return a.(PackageID) < b.(PackageID)
+ }),
+ }
+}
+
+func (m isActivePackageCacheMap) Clone() isActivePackageCacheMap {
+ return isActivePackageCacheMap{
+ impl: m.impl.Clone(),
+ }
+}
+
+func (m isActivePackageCacheMap) Destroy() {
+ m.impl.Destroy()
+}
+
+func (m isActivePackageCacheMap) Get(key PackageID) (bool, bool) {
+ value, ok := m.impl.Get(key)
+ if !ok {
+ return false, false
+ }
+ return value.(bool), true
+}
+
+func (m isActivePackageCacheMap) Set(key PackageID, value bool) {
+ m.impl.Set(key, value, nil)
+}
+
type parseKeysByURIMap struct {
impl *persistent.Map
}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 52b141a..aca46dd 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -225,27 +225,28 @@
},
}
v.snapshot = &snapshot{
- id: snapshotID,
- view: v,
- backgroundCtx: backgroundCtx,
- cancel: cancel,
- initializeOnce: &sync.Once{},
- generation: s.cache.store.Generation(generationName(v, 0)),
- packages: newPackagesMap(),
- meta: &metadataGraph{},
- files: newFilesMap(),
- goFiles: newGoFilesMap(),
- parseKeysByURI: newParseKeysByURIMap(),
- symbols: make(map[span.URI]*symbolHandle),
- actions: make(map[actionKey]*actionHandle),
- workspacePackages: make(map[PackageID]PackagePath),
- unloadableFiles: make(map[span.URI]struct{}),
- parseModHandles: make(map[span.URI]*parseModHandle),
- parseWorkHandles: make(map[span.URI]*parseWorkHandle),
- modTidyHandles: make(map[span.URI]*modTidyHandle),
- modWhyHandles: make(map[span.URI]*modWhyHandle),
- knownSubdirs: newKnownDirsSet(),
- workspace: workspace,
+ id: snapshotID,
+ view: v,
+ backgroundCtx: backgroundCtx,
+ cancel: cancel,
+ initializeOnce: &sync.Once{},
+ generation: s.cache.store.Generation(generationName(v, 0)),
+ packages: newPackagesMap(),
+ meta: &metadataGraph{},
+ files: newFilesMap(),
+ isActivePackageCache: newIsActivePackageCacheMap(),
+ goFiles: newGoFilesMap(),
+ parseKeysByURI: newParseKeysByURIMap(),
+ symbols: make(map[span.URI]*symbolHandle),
+ actions: make(map[actionKey]*actionHandle),
+ workspacePackages: make(map[PackageID]PackagePath),
+ unloadableFiles: make(map[span.URI]struct{}),
+ parseModHandles: make(map[span.URI]*parseModHandle),
+ parseWorkHandles: make(map[span.URI]*parseWorkHandle),
+ modTidyHandles: make(map[span.URI]*modTidyHandle),
+ modWhyHandles: make(map[span.URI]*modWhyHandle),
+ knownSubdirs: newKnownDirsSet(),
+ workspace: workspace,
}
// Initialize the view without blocking.
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index 85232ea..36dcafe 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -91,6 +91,10 @@
// It may be invalidated when a file's content changes.
packages packagesMap
+ // isActivePackageCache maps package ID to the cached value if it is active or not.
+ // It may be invalidated when metadata changes or a new file is opened or closed.
+ isActivePackageCache isActivePackageCacheMap
+
// actions maps an actionkey to its actionHandle.
actions map[actionKey]*actionHandle
@@ -144,6 +148,7 @@
func (s *snapshot) Destroy(destroyedBy string) {
s.generation.Destroy(destroyedBy)
s.packages.Destroy()
+ s.isActivePackageCache.Destroy()
s.files.Destroy()
s.goFiles.Destroy()
s.parseKeysByURI.Destroy()
@@ -754,24 +759,20 @@
s.mu.Lock()
defer s.mu.Unlock()
- seen := make(map[PackageID]bool)
for id := range s.workspacePackages {
- if s.isActiveLocked(id, seen) {
+ if s.isActiveLocked(id) {
ids = append(ids, id)
}
}
return ids
}
-func (s *snapshot) isActiveLocked(id PackageID, seen map[PackageID]bool) (active bool) {
- if seen == nil {
- seen = make(map[PackageID]bool)
- }
- if seen, ok := seen[id]; ok {
+func (s *snapshot) isActiveLocked(id PackageID) (active bool) {
+ if seen, ok := s.isActivePackageCache.Get(id); ok {
return seen
}
defer func() {
- seen[id] = active
+ s.isActivePackageCache.Set(id, active)
}()
m, ok := s.meta.metadata[id]
if !ok {
@@ -785,13 +786,18 @@
// TODO(rfindley): it looks incorrect that we don't also check GoFiles here.
// If a CGo file is open, we want to consider the package active.
for _, dep := range m.Deps {
- if s.isActiveLocked(dep, seen) {
+ if s.isActiveLocked(dep) {
return true
}
}
return false
}
+func (s *snapshot) resetIsActivePackageLocked() {
+ s.isActivePackageCache.Destroy()
+ s.isActivePackageCache = newIsActivePackageCacheMap()
+}
+
const fileExtensions = "go,mod,sum,work"
func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
@@ -1287,6 +1293,7 @@
}
}
s.meta = g.Clone(updates)
+ s.resetIsActivePackageLocked()
}
// noValidMetadataForURILocked reports whether there is any valid metadata for
@@ -1377,7 +1384,7 @@
var open []source.VersionedFileHandle
s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
- if s.isOpenLocked(fh.URI()) {
+ if isFileOpen(fh) {
open = append(open, fh)
}
})
@@ -1386,6 +1393,10 @@
func (s *snapshot) isOpenLocked(uri span.URI) bool {
fh, _ := s.files.Get(uri)
+ return isFileOpen(fh)
+}
+
+func isFileOpen(fh source.VersionedFileHandle) bool {
_, open := fh.(*overlay)
return open
}
@@ -1695,28 +1706,29 @@
newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1))
bgCtx, cancel := context.WithCancel(bgCtx)
result := &snapshot{
- id: s.id + 1,
- generation: newGen,
- view: s.view,
- backgroundCtx: bgCtx,
- cancel: cancel,
- builtin: s.builtin,
- initializeOnce: s.initializeOnce,
- initializedErr: s.initializedErr,
- packages: s.packages.Clone(),
- actions: make(map[actionKey]*actionHandle, len(s.actions)),
- files: s.files.Clone(),
- goFiles: s.goFiles.Clone(),
- parseKeysByURI: s.parseKeysByURI.Clone(),
- symbols: make(map[span.URI]*symbolHandle, len(s.symbols)),
- workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)),
- unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)),
- parseModHandles: make(map[span.URI]*parseModHandle, len(s.parseModHandles)),
- parseWorkHandles: make(map[span.URI]*parseWorkHandle, len(s.parseWorkHandles)),
- modTidyHandles: make(map[span.URI]*modTidyHandle, len(s.modTidyHandles)),
- modWhyHandles: make(map[span.URI]*modWhyHandle, len(s.modWhyHandles)),
- knownSubdirs: s.knownSubdirs.Clone(),
- workspace: newWorkspace,
+ id: s.id + 1,
+ generation: newGen,
+ view: s.view,
+ backgroundCtx: bgCtx,
+ cancel: cancel,
+ builtin: s.builtin,
+ initializeOnce: s.initializeOnce,
+ initializedErr: s.initializedErr,
+ packages: s.packages.Clone(),
+ isActivePackageCache: s.isActivePackageCache.Clone(),
+ actions: make(map[actionKey]*actionHandle, len(s.actions)),
+ files: s.files.Clone(),
+ goFiles: s.goFiles.Clone(),
+ parseKeysByURI: s.parseKeysByURI.Clone(),
+ symbols: make(map[span.URI]*symbolHandle, len(s.symbols)),
+ workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)),
+ unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)),
+ parseModHandles: make(map[span.URI]*parseModHandle, len(s.parseModHandles)),
+ parseWorkHandles: make(map[span.URI]*parseWorkHandle, len(s.parseWorkHandles)),
+ modTidyHandles: make(map[span.URI]*modTidyHandle, len(s.modTidyHandles)),
+ modWhyHandles: make(map[span.URI]*modWhyHandle, len(s.modWhyHandles)),
+ knownSubdirs: s.knownSubdirs.Clone(),
+ workspace: newWorkspace,
}
// Copy all of the FileHandles.
@@ -1975,9 +1987,10 @@
result.meta = s.meta
}
- // Update workspace packages, if necessary.
+ // Update workspace and active packages, if necessary.
if result.meta != s.meta || anyFileOpenedOrClosed {
result.workspacePackages = computeWorkspacePackagesLocked(result, result.meta)
+ result.resetIsActivePackageLocked()
} else {
result.workspacePackages = s.workspacePackages
}