internal/lsp/cache: symbolize in parallel

This change parallelizes the buildSymbolHandle().Get computation for
each file, with 2xGOMAXPROCS goroutines, since it is a mix of I/O (read)
and CPU (parse). (The symbolize AST walk happens in other goroutines.)

This reduces the time for the source.WorkspaceSymbols trace task
applied to kubernetes from 3981ms to 630ms (6x faster).

Change-Id: I5f1ee4afc2f6b2dd752791a30d33a21f50180a9c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/412818
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Alan Donovan 2022-06-16 14:03:39 -04:00
parent 88325aa063
commit e9870152b0
1 changed files with 27 additions and 16 deletions

View File

@ -17,6 +17,7 @@ import (
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
@ -25,6 +26,7 @@ import (
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"golang.org/x/sync/errgroup"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/event"
@ -994,26 +996,35 @@ func (s *snapshot) activePackageHandles(ctx context.Context) ([]*packageHandle,
return phs, nil
}
// Symbols extracts and returns the symbols for each file in all the snapshot's views.
func (s *snapshot) Symbols(ctx context.Context) (map[span.URI][]source.Symbol, error) {
result := make(map[span.URI][]source.Symbol)
// Keep going on errors, but log the first failure. Partial symbol results
// are better than no symbol results.
var firstErr error
// Keep going on errors, but log the first failure.
// Partial results are better than no symbol results.
var (
group errgroup.Group
nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU
iolimit = make(chan struct{}, nprocs) // I/O limiting counting semaphore
resultMu sync.Mutex
result = make(map[span.URI][]source.Symbol)
)
for uri, f := range s.files {
sh := s.buildSymbolHandle(ctx, f)
v, err := sh.handle.Get(ctx, s.generation, s)
if err != nil {
if firstErr == nil {
firstErr = err
uri, f := uri, f
// TODO(adonovan): upgrade errgroup and use group.SetLimit(nprocs).
iolimit <- struct{}{} // acquire token
group.Go(func() error {
defer func() { <-iolimit }() // release token
v, err := s.buildSymbolHandle(ctx, f).handle.Get(ctx, s.generation, s)
if err != nil {
return err
}
continue
}
data := v.(*symbolData)
result[uri] = data.symbols
resultMu.Lock()
result[uri] = v.(*symbolData).symbols
resultMu.Unlock()
return nil
})
}
if firstErr != nil {
event.Error(ctx, "getting snapshot symbols", firstErr)
if err := group.Wait(); err != nil {
event.Error(ctx, "getting snapshot symbols", err)
}
return result, nil
}