perf(mcp): 2.2× codedb_context — line-offset-cache windows for top sites#646
Conversation
The 'Top sites (±2 lines)' phase walked every byte of each top file from offset 0 once per hit to find its [line-2 .. line+2] window — 63% of codedb_context wall time (sites 122.9µs of 195.1µs on the bench corpus). Borrow the canonical cached bytes (stable pointer, no arena copy) under the already-held shared lock and resolve both window edges through the line-offset cache (#611) in O(log n), keeping the scanning path as the fallback for uncached files (released contents / low-memory). Also ships CODEDB_CONTEXT_PROFILE=1: per-phase ns breakdown of the composer on stderr (reader/cand/kwloop/rank/render/sites/emit), same pattern as CODEDB_LOAD_PROFILE — it is what located this hotspot. Gated bench (scripts/compare-bench.py, same machine, CI recipe): codedb_context 224.8µs -> 102.6µs (-54.4%); every other tool OK phase profile: sites 122.9µs -> 0.9µs; total 195.1µs -> 70.9µs Output is byte-identical modulo the response's own embedded elapsed-time string (mcpFormatDuration) rendering fewer digits because the call got faster. zig build test: 23/23 steps, 865/865. e2e_mcp_test.py: 20/20. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Benchmark Regression ReportThresholds: 10.00% and 50,000 ns absolute delta
|
|
Post-merge addendum for the record — closing the last theoretical gap in the byte-parity claim: The fast path borrows content via |
…ineOffsetCache (#653) The BM25 top-k materialization loop called extractLineByNumber per result — a scan from byte 0 of the whole file — up to max_results times per ranked query. Resolve the hit line through the line-offset cache (O(log n)) exactly like the exact-recall tiers (#611) and the context sites phase (#646) already do, keeping the scanning path as the fallback when the offset table can't be built. Same-session gated bench: codedb_context 129.0µs -> 113.6µs (-12%, context's keyword loop materializes ranked hits); codedb_search flat on the tiny hit-cached bench corpus — the win scales with file size on real repos, where each materialized hit was a whole-file walk. Full suite 23/23 steps, 869/873 (4 platform skips). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
What
codedb_contextis the heaviest gated bench case (3.6× the next tool) and the flagship first-touch call — and its "Top sites (±2 lines)" phase was 63% of the wall time: for every hit in every top file it walked the whole file byte-by-byte from offset 0 to locate the[line-2 .. line+2]window.This PR resolves both window edges through the existing #611
LineOffsetCachein O(log n) instead, borrowing the canonical cached bytes (stable pointer — the cache keys on it; also kills the per-file arena copy) under the shared lock the phase already holds. Uncached files (released contents /CODEDB_LOW_MEMORY) keep the old scanning path as a fallback, so behavior is unchanged everywhere the fast path can't prove itself.Two small
Exploreraccessors (cachedContentLocked,lineSpansFor) expose the existing private machinery; no new data structures.Also ships
CODEDB_CONTEXT_PROFILE=1— per-phase ns breakdown of the composer on stderr (same pattern asCODEDB_LOAD_PROFILE). It's what located this hotspot, and it maps the next ones: the per-keyword search loop (48% of what remains) and the callers/callees render (32%).Numbers (CI-identical recipe:
run-bench-json.py+compare-bench.py, same machine)codedb_contextavgEvery other gated tool: OK (no regressions; most improved slightly from run noise).
Quality
lineSpans's split semantics).response_bytesdelta in the bench is the response's own embedded elapsed-time string (mcpFormatDurationat mcp.zig runToolCall/handleCall) rendering fewer digits because the call got faster — confirmed by A/B'ing the binaries: the only differing bytes are the duration digits.zig build test: 23/23 steps, 865/865.e2e_mcp_test.py: 20/20.🤖 Generated with Claude Code