Ledger of the cold-start / index-cost investigation from the benchmark-baseline round (tooling in #605). Numbers from Linux x86_64, RelWithDebInfo, minimum of repeated runs; reproduce with the recipes in benchmarks/README.md.
Cold-start account (small TU, <string> + <vector> preamble)
client didOpen -> first diagnostics ~440ms
= PCH compile 260ms (clangd --check preamble: 220ms)
+ preamble index + links + blob 82ms (clangd: deferred to background)
+ PCH disk flush 2ms
+ main-file AST over the PCH 41ms (clangd "Building AST": ~46ms)
+ two IPC hops + master scheduling ~8ms
The PCH compile itself is at parity with clangd modulo TokenBuffer collection (~+40ms; preprocessing alone measures 18ms -> 32ms with tokens on). The TokenBuffer is consumed by the preamble indexing (the Semantics table reads expanded tokens), so both extra costs are two faces of one design decision: clice indexes the preamble synchronously inside the cold-start critical path; clangd does not. On heavy TUs the same structure scales to seconds (PCH build 2-5s, index share 15-25%).
Transport and copies are clean on this path: the PreambleState blob is written to disk by the worker and never crosses IPC (the master receives paths + diagnostics, KB-sized), and the whole pipeline residual is ~8ms. Warm requests: worker-side 0.11ms vs client-observed 0.74ms, ~0.6ms per round trip.
Index pass internals ([perf:index_detail], run any workload with pipeline_benchmark --log-level info)
Heavy TU (src/semantic/semantics.cpp, ~6.4s parse):
TUIndex::build 650ms = semantics table 82-158ms
+ projection 500-530ms <- dominant
+ include graph + dedup 70ms
serialize 137ms = path-rekeying copy 11-17ms
+ flatbuffers pack 126-155ms <- dominant
- Projection is the index hot spot (~75-80% of the build): walking semantics rows and emitting occurrences/relations per file. Any index-build optimization starts here.
- The suspected "accidental copy" exists but is small:
TUIndex::serialize deep-copies every FileIndex while rekeying FileID -> path_id (path_file_indices[...] = file_index). Measured 11-17ms on a heavy TU (~2% of the pass). Worth folding into a move once the serialize-twice contract is settled; not a priority.
- flatbuffers pack dominates serialization (~90%). Splitting builder-build vs finalize would need instrumentation inside kotatsu's codec; only worth it if this line ever becomes the bottleneck.
Directions (decisions pending, in rough order of leverage)
- Move preamble indexing off the critical path. Build the PCH, answer diagnostics, index asynchronously; index-backed features over preamble headers degrade until ready. Theoretical cold open lands at clangd's line (~270ms small TU). Needs a design for the degradation window and for
TokenBuffer (only collected when the async indexer runs).
- Skip re-indexing headers whose index is already on disk. A preamble covers thousands of headers; stdlib and third-party headers are rarely macro-sensitive in practice. If a header's content hash (and macro context) matches an existing shard, its projection can be skipped entirely. Interacts with the shard/content-hash model — needs its own design round.
- Projection micro-optimization — profile inside
project_semantics before touching anything.
Instrumentation for all of the above ships in #605 ([perf:build], [perf:index_detail], pipeline_benchmark, clangd --check recipe).
Ledger of the cold-start / index-cost investigation from the benchmark-baseline round (tooling in #605). Numbers from Linux x86_64, RelWithDebInfo, minimum of repeated runs; reproduce with the recipes in
benchmarks/README.md.Cold-start account (small TU,
<string>+<vector>preamble)The PCH compile itself is at parity with clangd modulo TokenBuffer collection (~+40ms; preprocessing alone measures 18ms -> 32ms with tokens on). The TokenBuffer is consumed by the preamble indexing (the Semantics table reads expanded tokens), so both extra costs are two faces of one design decision: clice indexes the preamble synchronously inside the cold-start critical path; clangd does not. On heavy TUs the same structure scales to seconds (PCH build 2-5s, index share 15-25%).
Transport and copies are clean on this path: the PreambleState blob is written to disk by the worker and never crosses IPC (the master receives paths + diagnostics, KB-sized), and the whole pipeline residual is ~8ms. Warm requests: worker-side 0.11ms vs client-observed 0.74ms, ~0.6ms per round trip.
Index pass internals (
[perf:index_detail], run any workload withpipeline_benchmark --log-level info)Heavy TU (
src/semantic/semantics.cpp, ~6.4s parse):TUIndex::serializedeep-copies everyFileIndexwhile rekeying FileID -> path_id (path_file_indices[...] = file_index). Measured 11-17ms on a heavy TU (~2% of the pass). Worth folding into a move once the serialize-twice contract is settled; not a priority.Directions (decisions pending, in rough order of leverage)
TokenBuffer(only collected when the async indexer runs).project_semanticsbefore touching anything.Instrumentation for all of the above ships in #605 (
[perf:build],[perf:index_detail],pipeline_benchmark,clangd --checkrecipe).