Commit Graph

5 Commits

Author SHA1 Message Date
Levi Neely 00f81f7f56 Perf: parallelize the full-N palette scan across the thread pool
PaletteModel::rebuild splits the bulk score-only scan into per-thread
chunks via QtConcurrent::blockingMapped when item count exceeds
kParallelThreshold (10000). The scan is a pure map over read-only data
(FuzzyRanker::score is a stateless static; m_items not mutated); chunks
are concatenated in order and finalizeVisible imposes a deterministic
sort, so the result is bit-identical to the serial path regardless of
thread timing. Incremental (type-forward) path stays serial — it only
touches <=1000 items.

First-scan at 397k symbols (mobydick, 16 cores): ~373ms -> ~61ms.
Link Qt6::Concurrent into the palette lib. New test
parallelScanIsCorrectAndDeterministic (40k items) guards correctness.
2026-10-08 11:38:32 +02:00
Levi Neely 1caa8d2221 Perf: prefer Universal Ctags, top-K cap + score-only + min query len
Universal Ctags: SymbolIndex::ctagsBinary() prefers ctags-universal over
the alternatives-managed ctags (Exuberant 5.9 has no Kotlin/TS parser).
Kotlin/TypeScript now indexed — symbol count on mobydick: 17k -> 397k.

At 397k, per-keystroke ranking was 0.3-0.7s. Three structural fixes:

1. FuzzyRanker: replace std::set with vector+sort+unique; add
   withRanges=false fast path that skips highlight computation during
   the bulk scan (ranges materialised only for displayed rows).
2. PaletteModel: cap visible results to top-1000 (partial_sort),
   score-only bulk, ranges for the top-K only. Nobody scrolls 400k.
3. Min query length (2): 1-char queries match everything and produce a
   useless, expensive full scan; below the threshold the palette shows
   the capped full list unranked. First real ranking at 2 chars; from
   there incremental narrowing makes every keystroke sub-2ms.

Measured (mobydick 397k symbols, RelWithDebInfo, type-forward):
  s: 337ms -> ~0ms | se: 507ms -> 373ms (1x first scan) |
  ser: 586ms -> 1.9ms | server: 198ms -> 0.8ms.

3 new tests (incrementalMatchesFullScan, visibleResultsAreCapped,
shortQueryDoesNotRank). docs/PERF.md updated.
2026-10-08 11:31:36 +02:00
Levi Neely a5b8d628b3 Perf: debounce palette filter + default to optimized build
Two fixes for the Alt+G typing lag:

1. Default CMAKE_BUILD_TYPE to RelWithDebInfo when unspecified — the
   shipped plugin was -O0. On mobydick (17k symbols) the worst
   type-forward keystroke drops ~340ms -> ~29ms (~12x).

2. Debounce the PaletteWidget filter with an 80ms single-shot timer so a
   fast typist's burst triggers one re-rank, not one per character.
   applyQueryNow() flushes the pending query on Enter so activation uses
   the latest text; activate() resets the debounce state.

The broad early-query keystrokes (1-2 chars matching most of N) are the
only ones still ~O(N); debounce ensures they are paid at most once per
pause. docs/PERF.md updated with measured numbers.
2026-10-08 11:13:37 +02:00
Levi Neely 2bf8d4a3c2 Perf: async + cached project indexing (fixes Alt+G open stall)
Add ProjectIndexer (project_lib, QtConcurrent + QFutureWatcher, 6 tests):
runs listFiles/listSymbols on the thread pool, delivers results on the UI
thread via filesReady/symbolsReady, caches per project root, and drops
stale results via a per-root generation counter.

Switchers now open the palette immediately — cache hit is instant, miss
shows a transient 'Indexing…' placeholder — and repopulate when the
background job finishes. The ~0.5-1s ctags stall on Alt+G no longer
blocks the UI thread. Cache invalidated on KateProjectBridge::
projectChanged. Register QList<Symbol> metatype; link Qt6::Concurrent.
Item building factored into buildFileItems/buildSymbolItems (shared by
the cache and async paths).
2026-10-08 11:02:52 +02:00
Levi Neely 8dd2995e36 Perf: measure switcher bottlenecks, add incremental re-rank
Add a benchmark harness (src/project/bench_switcher.cpp, manual target)
that times the Go-to-File/Symbol pipeline against a real project and
writes a metrics file. Measured on mobydick (12.2k files, 17.2k symbols);
findings in docs/PERF.md.

Bottleneck: PaletteModel::setQuery re-scored all N every keystroke
(Debug: 270-600ms/keystroke; cost grew with query length). Fix: when the
query is a pure append, re-score only the currently-visible subset — the
match set is monotonic under appending, so this is exact. Later keystrokes
drop from O(N) to O(matches) (symbols 'server' 30ms -> 9ms in Release).
Guarded by test_palettemodel::incrementalMatchesFullScan.

Also documented: shipped plugin is a Debug build (~5-8x slower than
Release), and ctags/listFiles run ~0.1-1s synchronously on open (move
off-thread = open follow-up).
2026-10-08 10:54:04 +02:00