Skip to content

Commit f487f36

Browse files
committed
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 <[email protected]> Run-TryBot: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 7b04e8b commit f487f36

File tree

1 file changed

+17
-14
lines changed

1 file changed

+17
-14
lines changed

internal/lsp/source/workspace_symbol.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,46 +83,48 @@ type matcherFunc func(chunks []string) (int, float64)
8383
// []string{"myType.field"} or []string{"myType.", "field"}.
8484
//
8585
// See the comment for symbolCollector for more information.
86-
type symbolizer func(name string, pkg Metadata, m matcherFunc) ([]string, float64)
86+
//
87+
// The space argument is an empty slice with spare capacity that may be used
88+
// to allocate the result.
89+
type symbolizer func(space []string, name string, pkg Metadata, m matcherFunc) ([]string, float64)
8790

88-
func fullyQualifiedSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
89-
_, score := dynamicSymbolMatch(name, pkg, matcher)
90-
if score > 0 {
91-
return []string{pkg.PackagePath(), ".", name}, score
91+
func fullyQualifiedSymbolMatch(space []string, name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
92+
if _, score := dynamicSymbolMatch(space, name, pkg, matcher); score > 0 {
93+
return append(space, pkg.PackagePath(), ".", name), score
9294
}
9395
return nil, 0
9496
}
9597

96-
func dynamicSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
98+
func dynamicSymbolMatch(space []string, name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
9799
var score float64
98100

99101
endsInPkgName := strings.HasSuffix(pkg.PackagePath(), pkg.PackageName())
100102

101103
// If the package path does not end in the package name, we need to check the
102104
// package-qualified symbol as an extra pass first.
103105
if !endsInPkgName {
104-
pkgQualified := []string{pkg.PackageName(), ".", name}
106+
pkgQualified := append(space, pkg.PackageName(), ".", name)
105107
idx, score := matcher(pkgQualified)
106108
nameStart := len(pkg.PackageName()) + 1
107109
if score > 0 {
108110
// If our match is contained entirely within the unqualified portion,
109111
// just return that.
110112
if idx >= nameStart {
111-
return []string{name}, score
113+
return append(space, name), score
112114
}
113115
// Lower the score for matches that include the package name.
114116
return pkgQualified, score * 0.8
115117
}
116118
}
117119

118120
// Now try matching the fully qualified symbol.
119-
fullyQualified := []string{pkg.PackagePath(), ".", name}
121+
fullyQualified := append(space, pkg.PackagePath(), ".", name)
120122
idx, score := matcher(fullyQualified)
121123

122124
// As above, check if we matched just the unqualified symbol name.
123125
nameStart := len(pkg.PackagePath()) + 1
124126
if idx >= nameStart {
125-
return []string{name}, score
127+
return append(space, name), score
126128
}
127129

128130
// If our package path ends in the package name, we'll have skipped the
@@ -131,7 +133,7 @@ func dynamicSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]strin
131133
if endsInPkgName && idx >= 0 {
132134
pkgStart := len(pkg.PackagePath()) - len(pkg.PackageName())
133135
if idx >= pkgStart {
134-
return []string{pkg.PackageName(), ".", name}, score
136+
return append(space, pkg.PackageName(), ".", name), score
135137
}
136138
}
137139

@@ -140,8 +142,8 @@ func dynamicSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]strin
140142
return fullyQualified, score * 0.6
141143
}
142144

143-
func packageSymbolMatch(name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
144-
qualified := []string{pkg.PackageName(), ".", name}
145+
func packageSymbolMatch(space []string, name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
146+
qualified := append(space, pkg.PackageName(), ".", name)
145147
if _, s := matcher(qualified); s > 0 {
146148
return qualified, s
147149
}
@@ -387,8 +389,9 @@ type symbolFile struct {
387389

388390
// matchFile scans a symbol file and adds matching symbols to the store.
389391
func matchFile(store *symbolStore, symbolizer symbolizer, matcher matcherFunc, roots []string, i symbolFile) {
392+
space := make([]string, 0, 3)
390393
for _, sym := range i.syms {
391-
symbolParts, score := symbolizer(sym.Name, i.md, matcher)
394+
symbolParts, score := symbolizer(space, sym.Name, i.md, matcher)
392395

393396
// Check if the score is too low before applying any downranking.
394397
if store.tooLow(score) {

0 commit comments

Comments
 (0)