Repository navigation
Description
Version
codebase-memory-mcp dev, built from cb0dfa64971a793e55170a3de9d2bd4c84f3d996 (v0.10.3-38-gcb0dfa64). The affected source files are unchanged on origin/main at 49d928be67322184fe25c8d15acd6f0e80b7e648.
Platform
macOS (Apple Silicon), 36 GiB unified memory, 18 logical CPUs.
Install channel
Built from source.
Binary variant
standard
What happened, and what did you expect?
Fast indexing of a large C/C++ repository spends most of its time in cross-file resolution. In c_lookup_member_depth, the namespaced-type fallback scans every registered type whenever direct, module-prefixed, alias, and base-class resolution miss:
for (int i = 0; i < ctx->registry->type_count; i++) {
// compare the final qualified-name segment
}On the public YDB corpus below, parallel_resolve took 857-908 seconds and accounted for about 71-72% of the pipeline. Worker sampling reached roughly 16 fully utilized CPU cores. I expected repeated short-name fallback lookups to use the finalized registry indexes instead of paying O(type_count) per lookup.
This is related to #1669's cross-LSP investigation, but it is not the same root cause. #1669 isolates per-file registry reconstruction for Java/Kotlin/PHP. This report isolates a C/C++ lookup over the shared prebuilt registry; the proposed index is deliberately built only for C/C++ so it does not add work to those per-file paths.
Reproduction
-
Check out the public corpus:
git clone https://github.com/ydb-platform/ydb.git git -C ydb checkout be88c989a45f90ea7b2372cb0f9098e0fa23a3d9
-
Build
codebase-memory-mcpfromcb0dfa64971a793e55170a3de9d2bd4c84f3d996, use a fresh cache, and run:CBM_PROFILE=1 CBM_WORKERS=18 CBM_CACHE_DIR=/tmp/cbm-ydb-baseline-cache \ ./codebase-memory-mcp cli index_repository \ '{"repo_path":"/path/to/ydb","name":"ydb-cpp-short-name-profile","mode":"fast","persistence":false}' -
Inspect the
pass.timingrows. Two byte-identical baseline runs and one local proof-of-concept run produced:build pipeline CLI wall parallel_resolvebaseline 1 1,255.481 s 1,261.89 s 907.684 s baseline 2 1,212.979 s 1,219.50 s 857.105 s short-name index 456.974 s 463.22 s 131.926 s Depending on the baseline, the proof of concept is 2.63-2.72x faster end to end and 6.50-6.88x faster in resolution.
The proof of concept builds an optional hash index from the final segment of each type's qualified name to an ascending chain of registry indices. C/C++ builds it once after registry finalization. The lookup walks that chain plus any post-finalize tail; allocation failure or an unindexed registry falls back to the original complete scan. The caller retains its exact dotted-suffix predicate, duplicate exclusion, and current-module preference.
Logs
No raw logs attached because they include local filesystem paths. Relevant aggregate profile rows are reproduced in the table above.
The baseline pipeline also reported:
parallel_extract=114.186 s
lsp_cross_prepare=78.333 s
parallel_resolve=907.684 s
dump_and_persist=106.995 s
Writer index sorting was 2.011 seconds (0.16% of the pipeline), so persistence or GPU work is not the first-order fix for this workload.
Diagnostics trajectory (memory / performance / leak issues)
Five-second process-family sampling observed baseline peak CPU at 1,594.7% and peak RSS at 13.20 GiB. Optimized runs reached 1,600.5% CPU; sampled peak RSS varied from 14.28 to 19.70 GiB, so this report makes no memory-improvement claim.
Both baselines used the same binary SHA-256 and all compared runs retained the same 106,939 file hashes and coverage counts. Full graph identity is naturally order-sensitive in this pipeline: the two byte-identical baseline runs themselves changed 222,076 common node payloads and tens of thousands of symmetric edge identities. The direct semantic guard is therefore a differential iterator test against the former linear scan, not a claim of byte-for-byte graph equality.
Local regression evidence for the proof of concept:
- 512 generated registry types across 23 repeated short names compare indexed output to the former ascending linear scan;
- post-finalize tail entries and unavailable-index fallback preserve complete candidate coverage;
- the caller still applies the exact predicate and current-module tie-break;
- the focused native C-LSP suite passes 760/760 with
-Werror.
Project scale (if relevant)
106,939 file hashes / 2,408,291 nodes / 11,397,892 edges in the first baseline; 225,399 Class nodes.
Proposed acceptance criteria
- C/C++ short-name fallback no longer scans the full finalized type registry for each lookup.
- Candidate order, exact filtering, module preference, post-finalize additions, and allocation-failure behavior remain unchanged.
- Languages that do not consume the index pay no new registry-finalization allocation or scan.
- Focused and repository-wide tests, lint, security, build, and DCO checks pass.
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (the public YDB repository), not proprietary code.
Activity
DeusData commentedon Aug 18, 2026
Thank you for the public-corpus benchmark, the repeated baseline, and the semantic differential guard. #1679 is linked as the focused implementation route, while this issue remains the performance evidence ledger.
The measured 2.63 to 2.72 times end-to-end improvement and the preservation requirements justify high-priority review. We will still review candidate ordering, post-finalize entries, allocation-failure fallback, and language isolation independently of the headline speedup. The queue is full, but the contribution has a clear route and a reproducible benchmark.
perf(lsp): index C++ short-name type lookups
DeusData commentedon Sep 1, 2026
Fixed on
mainby793716dc+32ae20d0, merged as7808686ceef1dae18ab2b6a02b3ecdb6729f4c1c(#1679).Measured on the public
ydb-platform/ydbcorpus atbe88c989a45f, fast mode, 18 workers, fresh isolated caches:parallel_resolve2.63–2.72x end to end, 6.50–6.88x in resolution, with the same 106,939 input file hashes and coverage counts across every run.
Two methodology notes worth keeping with the number: the two byte-identical baselines establish the run-to-run variance band, and they themselves differ by 222,076 common node payloads — so full graph identity was never available as the correctness gate here. The gate used instead is a differential iterator contract against the former linear scan, plus an explicit allocation-failure path that falls back to full-scan semantics and is fault-injected in
registry_short_name_indexes.No memory claim: peak RSS varied between runs.
Rollout is scoped to C, C++ and CUDA. Other languages pay no new
O(type_count)construction, and adoption elsewhere should be separate measured work with its own ambiguity and ordering tests.