kate-deft/docs/PERF.md

3.3 KiB
Raw Blame History

Switcher performance — measured bottlenecks

Benchmark harness: src/project/bench_switcher.cpp (manual target bench_switcher, not a ctest). Run:

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.

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).