Skip to content

perf(lsp): filter servers before searching PATH#3370

Open
TheJhyeFactor wants to merge 1 commit into
charmbracelet:mainfrom
TheJhyeFactor:perf/filter-lsp-before-path-lookup
Open

perf(lsp): filter servers before searching PATH#3370
TheJhyeFactor wants to merge 1 commit into
charmbracelet:mainfrom
TheJhyeFactor:perf/filter-lsp-before-path-lookup

Conversation

@TheJhyeFactor

@TheJhyeFactor TheJhyeFactor commented Jul 19, 2026

Copy link
Copy Markdown

Addresses #3072.

What I looked at

The reported PATH scan maps to Manager.startServer: for every bundled LSP, Crush called exec.LookPath before checking whether that server handled the file being viewed or edited. With roughly 300 server definitions and a wide PATH, a single file event could turn into thousands of filesystem checks for unrelated language servers.

The CPU profile confirmed that this is filesystem work rather than LSP startup work. In the lookup-first benchmark, 90.5% of CPU samples were in raw syscalls reached through os.Stat, and 99.3% of allocated space flowed through exec.LookPath.

What changed

  • Reject generic auto-start commands before doing filesystem work.
  • Check file types and root markers before searching PATH.
  • Keep the existing 30-second unavailable-server backoff for relevant servers.
  • Add regression coverage that verifies unrelated and generic servers never trigger a command lookup, while missing relevant servers are still cached.

This doesn't add a long-lived PATH cache, so installing an LSP during a session keeps the existing retry behaviour.

Benchmark

Apple M4, darwin/arm64, Go 1.26.5. The benchmark used 300 synthetic server definitions, 70 empty PATH directories, and one server matching a Go file. Five runs per order:

Order Median time Bytes/op Allocs/op
lookup PATH, then filter 50.82 ms 11,107,228 84,600
filter, then lookup PATH 308.61 us 46,656 884

That isolates a roughly 165x reduction in wall time, 238x less allocated memory, and 96x fewer allocations for the discovery scan. This is a controlled worst-case benchmark of LSP discovery, not an end-to-end application benchmark.

Validation

  • go test -race -failfast ./...
  • go build -race ./...
  • golangci-lint run --path-mode=abs --config=.golangci.yml --timeout=5m
  • go mod tidy with no diff
  • focused LSP tests repeated 10 times

Existing work

#3096 makes the unavailable-server retry delay configurable. This takes a different route: it avoids probing irrelevant servers in the first place, while leaving the current retry semantics unchanged.

@charmcli

charmcli commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@TheJhyeFactor

Copy link
Copy Markdown
Author

I have read the Contributor License Agreement (CLA) and hereby sign the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants