internal/lsp/source: reduce allocation in workspace-symbols
dynamicSymbolMatch is an allocation hotspot (9% of all bytes),
because it allocates a 3-element []string that quickly becomes
garbage. This change passes in an empty slice with spare capacity
allowing the same array to be reused throughout the matchFile loop.
BenchmarkSymbols on k8s shows -72% bytes, -88% allocs, -9% wall time.
Change-Id: Id20c7cd649874a212e4d4c5f1aa095277b044a5b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/415500
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/source/workspace_symbol.go b/internal/lsp/source/workspace_symbol.go
index 0822de0..6167c58 100644
--- a/internal/lsp/source/workspace_symbol.go
+++ b/internal/lsp/source/workspace_symbol.go
@@ -83,17 +83,19 @@
// []string{"myType.field"} or []string{"myType.", "field"}.
//
// See the comment for symbolCollector for more information.
-type symbolizer func(name string, pkg Metadata, m matcherFunc) ([]string, float64)
+//
+// The space argument is an empty slice with spare capacity that may be used
+// to allocate the result.
+type symbolizer func(space []string, name string, pkg Metadata, m matcherFunc) ([]string, float64)
-func fullyQualifiedSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
- _, score := dynamicSymbolMatch(name, pkg, matcher)
- if score > 0 {
- return []string{pkg.PackagePath(), ".", name}, score
+func fullyQualifiedSymbolMatch(space []string, name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
+ if _, score := dynamicSymbolMatch(space, name, pkg, matcher); score > 0 {
+ return append(space, pkg.PackagePath(), ".", name), score
}
return nil, 0
}
-func dynamicSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
+func dynamicSymbolMatch(space []string, name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
var score float64
endsInPkgName := strings.HasSuffix(pkg.PackagePath(), pkg.PackageName())
@@ -101,14 +103,14 @@
// If the package path does not end in the package name, we need to check the
// package-qualified symbol as an extra pass first.
if !endsInPkgName {
- pkgQualified := []string{pkg.PackageName(), ".", name}
+ pkgQualified := append(space, pkg.PackageName(), ".", name)
idx, score := matcher(pkgQualified)
nameStart := len(pkg.PackageName()) + 1
if score > 0 {
// If our match is contained entirely within the unqualified portion,
// just return that.
if idx >= nameStart {
- return []string{name}, score
+ return append(space, name), score
}
// Lower the score for matches that include the package name.
return pkgQualified, score * 0.8
@@ -116,13 +118,13 @@
}
// Now try matching the fully qualified symbol.
- fullyQualified := []string{pkg.PackagePath(), ".", name}
+ fullyQualified := append(space, pkg.PackagePath(), ".", name)
idx, score := matcher(fullyQualified)
// As above, check if we matched just the unqualified symbol name.
nameStart := len(pkg.PackagePath()) + 1
if idx >= nameStart {
- return []string{name}, score
+ return append(space, name), score
}
// If our package path ends in the package name, we'll have skipped the
@@ -131,7 +133,7 @@
if endsInPkgName && idx >= 0 {
pkgStart := len(pkg.PackagePath()) - len(pkg.PackageName())
if idx >= pkgStart {
- return []string{pkg.PackageName(), ".", name}, score
+ return append(space, pkg.PackageName(), ".", name), score
}
}
@@ -140,8 +142,8 @@
return fullyQualified, score * 0.6
}
-func packageSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
- qualified := []string{pkg.PackageName(), ".", name}
+func packageSymbolMatch(space []string, name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
+ qualified := append(space, pkg.PackageName(), ".", name)
if _, s := matcher(qualified); s > 0 {
return qualified, s
}
@@ -387,8 +389,9 @@
// matchFile scans a symbol file and adds matching symbols to the store.
func matchFile(store *symbolStore, symbolizer symbolizer, matcher matcherFunc, roots []string, i symbolFile) {
+ space := make([]string, 0, 3)
for _, sym := range i.syms {
- symbolParts, score := symbolizer(sym.Name, i.md, matcher)
+ symbolParts, score := symbolizer(space, sym.Name, i.md, matcher)
// Check if the score is too low before applying any downranking.
if store.tooLow(score) {