82 lines
3.3 KiB
Markdown
82 lines
3.3 KiB
Markdown
# Switcher performance — measured bottlenecks
|
||
|
||
Benchmark harness: `src/project/bench_switcher.cpp` (manual target `bench_switcher`,
|
||
not a ctest). Run:
|
||
|
||
```sh
|
||
cmake -S . -B build-rel -DCMAKE_BUILD_TYPE=Release
|
||
cmake --build build-rel --target bench_switcher
|
||
./build-rel/bin/bench_switcher <project-root> /tmp/switcher_metrics.txt
|
||
```
|
||
|
||
Reference project: `~/prj/r7.20/mobydick` — **12,234 files**, **17,184 ctags
|
||
symbols**, 6.3 GB tree.
|
||
|
||
## One-time cost when the switcher opens (UI thread, synchronous)
|
||
|
||
| Stage | Release | Debug (shipped) |
|
||
|----------------------------|--------:|----------------:|
|
||
| `ProjectIndex::listFiles` | ~84 ms | ~78 ms |
|
||
| `SymbolIndex::listSymbols` | ~974 ms | ~499 ms |
|
||
| build items + `setItems` | ~5 ms | ~45-70 ms |
|
||
|
||
- `listFiles` is a `git ls-files` subprocess — acceptable but blocks the UI.
|
||
- `listSymbols` is a **ctags subprocess (~0.5-1 s)** — the dominant open-cost,
|
||
and it blocks the UI thread. (Cost is in the external binary, not our parse.)
|
||
|
||
## Per-keystroke cost — the interactive bottleneck
|
||
|
||
`PaletteModel::setQuery` → re-rank. Originally O(N) every keystroke; the cost
|
||
*grew* with query length even as the result set shrank.
|
||
|
||
Full-scan (every keystroke re-scores all N), Release:
|
||
|
||
| Query | files | symbols |
|
||
|------------------|---------:|---------:|
|
||
| `server` | ~14 ms | ~30 ms |
|
||
| `src server go` | ~17 ms | ~36 ms |
|
||
|
||
Debug (what ships): **270-342 ms (files)**, **459-598 ms (symbols)** per
|
||
keystroke — unusable.
|
||
|
||
## Fix 1 — incremental narrowing (done)
|
||
|
||
When a query only **appends** to the previous one, re-score only the currently
|
||
visible subset (the match set is monotonic under lengthening/adding a needle),
|
||
turning later keystrokes from O(N) into O(previous matches). Exactness is
|
||
guarded by `test_palettemodel::incrementalMatchesFullScan` (incremental ==
|
||
full-scan, incl. the typo tier).
|
||
|
||
Effect once the set narrows (Release, type-forward):
|
||
|
||
| Query | full-scan | incremental |
|
||
|----------|----------:|------------:|
|
||
| files `server` | ~14 ms | **~6.6 ms** |
|
||
| symbols `server` | ~30 ms | **~9.0 ms** |
|
||
|
||
Early keystrokes (`s`, `se`) remain full-N because almost everything matches —
|
||
correct and unavoidable without a prefix index.
|
||
|
||
## Fix 2 — build the installed plugin optimized (recommended)
|
||
|
||
The shipped `olliepalette.so` is a **Debug** build (`-g`, no `-O`). Release is
|
||
~5-8× faster on every hot path above. Install a `Release` or `RelWithDebInfo`
|
||
build.
|
||
|
||
## Follow-up — move indexing off the UI thread (done)
|
||
|
||
`ProjectIndexer` (`src/project/projectindexer.{h,cpp}`, in `project_lib`, 6
|
||
tests) runs `listFiles` / `listSymbols` on the thread pool (`QtConcurrent` +
|
||
`QFutureWatcher`) and delivers results on the UI thread via `filesReady` /
|
||
`symbolsReady`, caching per project root. The switchers now:
|
||
|
||
- open the palette **immediately** (cache hit → instant; miss → a transient
|
||
"Indexing…" / "Indexing symbols…" placeholder), and
|
||
- repopulate via the ready signal when the background job finishes.
|
||
|
||
A per-root generation counter drops superseded/invalidated results. The cache
|
||
is dropped on `KateProjectBridge::projectChanged` so a project switch re-indexes.
|
||
Net effect: the UI thread never blocks on git/ctags; the ~1 s Alt+G stall is
|
||
gone (first open shows a placeholder and fills in; subsequent opens are instant
|
||
from cache).
|