internal/lsp/cache: use persistent map for storing packages in the snapshot
This on average reduces latency from 25ms to 12ms on internal codebase.
Updates golang/go#45686
Change-Id: I49c8f09f8e54b7b486d7ff7eb8f4ba9f0d90b278
Reviewed-on: https://go-review.googlesource.com/c/tools/+/413655
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index f09fc29..797298a 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -108,7 +108,7 @@
m := ph.m
key := ph.key
- h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
+ h, release := s.generation.GetHandle(key, func(ctx context.Context, arg memoize.Arg) interface{} {
snapshot := arg.(*snapshot)
// Begin loading the direct dependencies, in parallel.
@@ -128,14 +128,14 @@
wg.Wait()
return data
- }, nil)
+ })
ph.handle = h
// Cache the handle in the snapshot. If a package handle has already
// been cached, addPackage will return the cached value. This is fine,
// since the original package handle above will have no references and be
// garbage collected.
- ph = s.addPackageHandle(ph)
+ ph = s.addPackageHandle(ph, release)
return ph, nil
}
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index da0b246..3fb67a7 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -228,9 +228,9 @@
// TODO(rfindley): this should not be necessary, as we should have already
// invalidated in snapshot.clone.
for id := range invalidatedPackages {
- for _, mode := range []source.ParseMode{source.ParseHeader, source.ParseExported, source.ParseFull} {
+ for _, mode := range source.AllParseModes {
key := packageKey{mode, id}
- delete(s.packages, key)
+ s.packages.Delete(key)
}
}
diff --git a/internal/lsp/cache/maps.go b/internal/lsp/cache/maps.go
index 91b0e77..14026ab 100644
--- a/internal/lsp/cache/maps.go
+++ b/internal/lsp/cache/maps.go
@@ -155,3 +155,54 @@
func (m parseKeysByURIMap) Delete(key span.URI) {
m.impl.Delete(key)
}
+
+type packagesMap struct {
+ impl *persistent.Map
+}
+
+func newPackagesMap() packagesMap {
+ return packagesMap{
+ impl: persistent.NewMap(func(a, b interface{}) bool {
+ left := a.(packageKey)
+ right := b.(packageKey)
+ if left.mode != right.mode {
+ return left.mode < right.mode
+ }
+ return left.id < right.id
+ }),
+ }
+}
+
+func (m packagesMap) Clone() packagesMap {
+ return packagesMap{
+ impl: m.impl.Clone(),
+ }
+}
+
+func (m packagesMap) Destroy() {
+ m.impl.Destroy()
+}
+
+func (m packagesMap) Get(key packageKey) (*packageHandle, bool) {
+ value, ok := m.impl.Get(key)
+ if !ok {
+ return nil, false
+ }
+ return value.(*packageHandle), true
+}
+
+func (m packagesMap) Range(do func(key packageKey, value *packageHandle)) {
+ m.impl.Range(func(key, value interface{}) {
+ do(key.(packageKey), value.(*packageHandle))
+ })
+}
+
+func (m packagesMap) Set(key packageKey, value *packageHandle, release func()) {
+ m.impl.Set(key, value, func(key, value interface{}) {
+ release()
+ })
+}
+
+func (m packagesMap) Delete(key packageKey) {
+ m.impl.Delete(key)
+}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 4a7a5b2..8d8e63f 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -231,7 +231,7 @@
cancel: cancel,
initializeOnce: &sync.Once{},
generation: s.cache.store.Generation(generationName(v, 0)),
- packages: make(map[packageKey]*packageHandle),
+ packages: newPackagesMap(),
meta: &metadataGraph{},
files: newFilesMap(),
goFiles: newGoFilesMap(),
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index e94ad7b..39e958e 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -89,7 +89,7 @@
// packages maps a packageKey to a set of packageHandles to which that file belongs.
// It may be invalidated when a file's content changes.
- packages map[packageKey]*packageHandle
+ packages packagesMap
// actions maps an actionkey to its actionHandle.
actions map[actionKey]*actionHandle
@@ -140,6 +140,7 @@
func (s *snapshot) Destroy(destroyedBy string) {
s.generation.Destroy(destroyedBy)
+ s.packages.Destroy()
s.files.Destroy()
s.goFiles.Destroy()
s.parseKeysByURI.Destroy()
@@ -711,16 +712,17 @@
return s.meta.importedBy[id]
}
-func (s *snapshot) addPackageHandle(ph *packageHandle) *packageHandle {
+func (s *snapshot) addPackageHandle(ph *packageHandle, release func()) *packageHandle {
s.mu.Lock()
defer s.mu.Unlock()
// If the package handle has already been cached,
// return the cached handle instead of overriding it.
- if ph, ok := s.packages[ph.packageKey()]; ok {
- return ph
+ if result, ok := s.packages.Get(ph.packageKey()); ok {
+ release()
+ return result
}
- s.packages[ph.packageKey()] = ph
+ s.packages.Set(ph.packageKey(), ph, release)
return ph
}
@@ -1090,10 +1092,10 @@
defer s.mu.Unlock()
results := map[string]source.Package{}
- for _, ph := range s.packages {
+ s.packages.Range(func(key packageKey, ph *packageHandle) {
cachedPkg, err := ph.cached(s.generation)
if err != nil {
- continue
+ return
}
for importPath, newPkg := range cachedPkg.imports {
if oldPkg, ok := results[string(importPath)]; ok {
@@ -1105,7 +1107,7 @@
results[string(importPath)] = newPkg
}
}
- }
+ })
return results, nil
}
@@ -1134,7 +1136,8 @@
id: id,
mode: mode,
}
- return s.packages[key]
+ ph, _ := s.packages.Get(key)
+ return ph
}
func (s *snapshot) getSymbolHandle(uri span.URI) *symbolHandle {
@@ -1690,7 +1693,7 @@
builtin: s.builtin,
initializeOnce: s.initializeOnce,
initializedErr: s.initializedErr,
- packages: make(map[packageKey]*packageHandle, len(s.packages)),
+ packages: s.packages.Clone(),
actions: make(map[actionKey]*actionHandle, len(s.actions)),
files: s.files.Clone(),
goFiles: s.goFiles.Clone(),
@@ -1894,13 +1897,12 @@
addRevDeps(id, invalidateMetadata)
}
- // Copy the package type information.
- for k, v := range s.packages {
- if _, ok := idsToInvalidate[k.id]; ok {
- continue
+ // Delete invalidated package type information.
+ for id := range idsToInvalidate {
+ for _, mode := range source.AllParseModes {
+ key := packageKey{mode, id}
+ result.packages.Delete(key)
}
- newGen.Inherit(v.handle)
- result.packages[k] = v
}
// Copy the package analysis information.
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 73e1b7f..98d1151 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -478,6 +478,10 @@
ParseFull
)
+// AllParseModes contains all possible values of ParseMode.
+// It is used for cache invalidation on a file content change.
+var AllParseModes = []ParseMode{ParseHeader, ParseExported, ParseFull}
+
// TypecheckMode controls what kind of parsing should be done (see ParseMode)
// while type checking a package.
type TypecheckMode int