Home/Benchmarks

Benchmarks

Measured on real code, reproducibly.

Not a synthetic corpus. These numbers come from the actual release binary run against real codebases and a hand-curated NL→code gold set, by the harness in bench/run_bench.py.

In plain terms These are real measurements, not estimates. The headline: searching by meaning finds the right file about twice as often as classic keyword search, an edit re-indexes almost instantly, and everyday file operations stay fast. Every chart on this page is generated straight from the raw results, and each section lists the single command you can run to reproduce it yourself.

How to read these numbers

Four terms carry most of this page. None of them is complicated, but a number like “MRR 0.67” means nothing without a sense of what good looks like — so here is the whole vocabulary, once, with a reference point.

recall@1
How often the correct file is the very first result. This is the one that matters most for an agent: at rank 1 it can answer immediately; at rank 4 it opens four files first. recall@5 and recall@10 are the same idea with a wider net — “was it anywhere in the top 5 / top 10”.
MRR
Mean reciprocal rank — average of 1/rank across all queries. It compresses “how far down the list was the answer” into one number: 1.0 means always first, 0.5 means typically second, 0.2 means typically fifth. Unlike recall@1 it rewards getting closer even when you miss the top slot.
p50 / p99
The typical case and the bad case. p50 is the median — half of all runs were faster. p99 is the slowest 1%. Averages hide stalls, so both are reported: a system with a good p50 and a terrible p99 feels unreliable even though it looks fine on paper.
chunk
The unit everything is counted in. SynaFS does not index whole files — it splits code at function and method boundaries, so a “chunk” is roughly one function with its doc comment attached. That is why corpus sizes appear as “355 files / 1,873 chunks”: the file count is what you wrote, the chunk count is what actually gets embedded and searched.
The reference point: grep scores MRR ≈ 0.28. On the same 23-query gold set, a plain grep workflow scores MRR 0.275 and puts the right file first only 4% of the time. That is the floor every number here is measured against — a semantic index that scores below it is worse than doing nothing, which is why the repository ships a regression gate that fails the build if MRR drops under 0.245. For contrast, the neural index scores MRR 0.670 with the right file first 57% of the time.
Results

What the runs show

Every bar below is computed straight from bench/results.json by bench/make_charts.py — no hand-drawn numbers. The headline: swapping in the local semantic embedder roughly doubles MRR and recall@1 on paraphrased queries.

Retrieval quality — semantic vs lexical

recall@k and MRR · higher is better · 18-query NL→code gold set

0.0 0.2 0.4 0.6 0.33 0.11 R@1 0.56 0.28 R@3 0.56 0.39 R@5 0.61 0.44 R@10 0.43 0.21 MRR semantic (CodeRankEmbed) lexical (hash)

Per-query rank — lexical vs semantic

rank of the gold file, 1 = best · grey = lexical, blue = semantic · ✕ = not in top-10

1 3 5 10 miss sys/uring.rs index/lib.rs sys/device.rs web/tls.rs engine/fanotify.rs embed/coderank.rs web/lib.rs fuse/resolver.rs chunk/lib.rs store/lib.rs query/lib.rs grpc/hpack.rs engine/versions.rs web/ws.rs engine/wal.rs index/graph.rs engine/versions.rs engine/state.rs

Indexing throughput

chunks / second on real repos · hash embedder

nidavellir 32,438/s SynaFS 7,493/s rogers 2,988/s chunks/s · hash embedder · higher is better

Search latency

p50 / p99 · in-process · lower is better

p50 0.21 ms p99 0.25 ms in-process · 5,000-chunk engine

Embed-cache hit rate

on a 1-line edit

80% cached · 80% re-embedded · 20% touched chunks only
Live

Watch every experiment run

This is not a mock-up. We recorded the actual session — building the binary, then running the whole suite end to end (A retrieval quality · B indexing · C engine latency · D filesystem) — and replay it below with its real timing (long idle pauses are compressed). Here is the exact machine it ran on.

OS
Ubuntu 24.04.4 LTS · Linux 6.17 · x86-64
CPU
AMD Ryzen AI MAX+ PRO 395 · 32 cores
Memory
94 GB
Toolchain
rustc 1.96.0 · cargo 1.96.0 · Python 3.12.3
Embedder
CodeRankEmbed (137M, 768-dim) · pure-Rust candle · CPU inference
synafs@bench: ~/SynaFS
# press play to replay the recorded benchmark run

Recorded with bench/record_cast.py (every line timestamped); replayed by a tiny vanilla-JS player — no asciinema, no external libraries. The same run wrote bench/results.json that powers the charts above.

↓ The run wrote results.json

The exact file the run just produced — shown verbatim, then charted straight from it.

Retrieval quality, charted from results.json

recall@k and MRR · higher is better · 18-query NL→code gold set

Methodology

How we experimented

SynaFS ships a built-in syna bench, but its corpus is synthetic — recall is 1.0 by construction, which proves the pipeline runs but says nothing about quality. The harness here is different: it indexes real source trees and grades retrieval against a gold set whose answers are fixed by inspection, swapping only the embedder so the comparison is clean.

  1. Build the release binary with the local semantic embedder: cargo build --release --features coderank.
  2. Assemble corpora — copy code files only (excluding node_modules, target, build output) from real repositories into a clean tree.
  3. Write 18 natural-language queries, each a paraphrase that avoids the codebase's own identifiers; fix the gold answer to the file that primarily implements that concept.
  4. Index the SynaFS tree twice — once with the offline lexical hash embedder, once with CodeRankEmbed — and run the identical hybrid pipeline (RRF over vector + BM25 + trigram).
  5. Grade at file level: a hit counts if its path ends with the gold path. recall@k = share of queries with a gold hit in the top k; MRR = mean of 1/rank.
  6. Measure engine latency in-process via syna bench; measure indexing throughput by wall-clock over the real trees.

Gold-set examples (the queries contain none of the target's identifiers)

Natural-language queryGold file
“compress response header fields for an http2 stream”syna-grpc/src/hpack.rs
“get notified of every write across a whole mounted filesystem”syna-engine/src/fanotify.rs
“demand and check the caller's certificate during the secure handshake”syna-web/src/tls.rs
“run a neural code representation model locally without python”syna-embed/src/coderank.rs

Reference machine: x86-64 Linux, CPU-only inference, the pure-Rust engine (brute-force vector search over an in-memory snapshot). Your numbers will vary by corpus, embedder, and hardware. Raw output lives in bench/results.json.

A

Retrieval quality — semantic vs lexical

bench/gold.json is 18 natural-language queries over the SynaFS source. Each is a paraphrase that deliberately avoids the codebase's own identifiers; the gold answer is the file that primarily implements that concept (e.g. "compress response header fields for an http2 stream"syna-grpc/src/hpack.rs). Relevance is graded at file level. We index the same tree twice and run the identical hybrid pipeline, changing only the embedder.

Embedderrecall@1recall@3recall@5recall@10MRR
Lexical (hash baseline)0.1110.2780.3890.5000.216
Semantic (CodeRankEmbed)0.3330.5560.5560.6110.434

Semantic embeddings roughly double MRR (0.210 → 0.434) and recall@1 (0.111 → 0.333). The biggest per-query wins are exactly where lexical overlap is weakest: "get notified of every write across a whole mounted filesystem"fanotify.rs climbs from rank 10 → 1, and "demand and check the caller's certificate during the secure handshake"tls.rs from 6 → 1. Several hard queries (hpack.rs, wal.rs, ws.rs) are missed by both in the top-10 — the set is small and untuned, not rigged toward a win.

Scope. 18 queries, file-level gold, author-written. This measures the value of the semantic signal inside SynaFS's own pipeline — not a leaderboard ranking against external systems. A standard benchmark (CoIR / CodeSearchNet) is future work.
B

Indexing throughput — real repositories

End-to-end syna index (tree-walk → tree-sitter chunk → embed → persist) over real source trees, code files only. The offline hash embedder isolates engine/chunking throughput; semantic indexing is bound by CPU model inference and is far slower (it is the quality path, not the throughput path).

CorpusFilesChunksfiles/sMB/schunks/sIndex
SynaFS (self)609644665.77,4937 MB
rogers4674,3043244.42,98834 MB
nidavellir55921,60483964.232,438198 MB

Index size is the current split snapshot (manifest.json + docs.bin + units.ndjson): full vectors still dominate disk. PQ is shipped as an opt-in scale path (SYNA_ANN=pq); making it automatic at large N remains future work.

C

Engine latency — in-process

From syna bench at 1,000 files / 5,000 chunks. These are in-process (no per-call snapshot reload), so they reflect engine latency, not CLI start-up. Corpus is synthetic; the latency is real.

Metricp50p99
Search latency0.21 ms0.25 ms
1-line reindex latency19.5 ms21.3 ms
D

Filesystem performance — the POSIX floor

SynaFS is a real FUSE filesystem, so we measured ordinary file ops through the mount against the raw backing disk, plus the cost of a semantic search done by listing a magic directory. The honest picture: metadata is essentially free, every latency stays under ~100 µs, and a semantic query runs in a fraction of a millisecond — but raw byte streaming carries real overhead, because the current pure-Rust FUSE copies bytes through userspace (kernel FUSE_PASSTHROUGH would close that gap).

Byte throughput vs raw disk

sequential read & write · mount as a share of raw

read · raw 32.2 GB/s mount 6.4 GB/s · 20% of raw write · raw 2.5 GB/s mount 0.7 GB/s · 28% of raw warm cache · pure-Rust userspace FUSE (no kernel passthrough yet)

Metadata latency · raw vs mount

stat / readdir / open+read · p50 · lower is better

stat 3.8 4.1 µs readdir 2.5 19.0 µs open+read 5.6 23.0 µs raw mount · µs (p50)

Search by ls

semantic query through the magic path

0.37 ms · p50 ls /.syna/query/<text>/ — a search by readdir mean 2.7 ms (p99 includes cold first query)
Operationrawmount
Sequential read32.2 GB/s6.4 GB/s
Sequential write (index-on-write)2.5 GB/s0.7 GB/s
stat / getattr3.8 µs4.1 µs
readdir2.5 µs19.0 µs
open + read (small file)5.6 µs23.0 µs
Semantic query · ls magic path0.37 ms
Scope. Warm page cache, single host, one FUSE worker. stat is identical through the mount because attributes are cached; throughput is lower because reads/writes round-trip through userspace and writes also enqueue the reindex. Magic-path semantic query is 0.37 ms p50 / 71 ms p99 in the checked-in run, so the median is the fast path, not the full latency envelope. Reproduce: python3 bench/fs_bench.py.
E

Scaling — overcoming brute-force with ANN

Exact brute-force vector search is O(N) per query, so SynaFS includes a pure-Rust HNSW approximate-nearest-neighbour index behind the same VectorIndex trait and selectable with SYNA_ANN=hnsw. The current checked-in public artifact is a 10k-vector smoke run: 2.6× faster p50 search with recall 1.000. Full 100k headlines remain pending until regenerated and committed.

Search speedup vs scale

HNSW vs exact brute-force · by corpus size

10k 2.6× HNSW search vs brute-force · higher is better

Recall held as it scales

recall@10 vs exact brute-force · 0–1

10k 1.000 recall@10 vs exact · 1.0 = identical to brute-force

Latency at 10k

p50 search · brute-force vs HNSW

brute-force 0.79 ms HNSW 0.30 ms p50 search at 10k vectors · 3× apart
Vectorssearch p50 · brute → HNSWrecall@10speedupHNSW build
10,0000.79 → 0.30 ms1.0002.6×2.7 s
Scope. Clustered synthetic vectors. The published checked-in artifact is a 10k-vector smoke run: p50 0.79 → 0.30 ms, p99 1.01 → 0.38 ms, recall 1.000, build 2.7 s. 100k HNSW headline numbers are intentionally withheld until a fresh sweep is committed. Reproduce: syna ann-bench --sizes 10000 --dim 768 --queries 200.
F

Memory — 32× smaller with product quantization

M0 kept every vector as full f32 — ~3 KB each, so a few million chunks no longer fit in RAM. SynaFS now ships a pure-Rust PQ-compressed index (SYNA_ANN=pq): each vector becomes a 96-byte code, the raw f32s move to an on-disk tier, and search re-scores the top candidates exactly from disk — so RAM drops 32× while recall@10 stays at brute-force. Reproduce with syna pq-bench.

RAM footprint at 100k vectors

full f32 vs PQ codes · lower is better

full f32 307.2 MB PQ codes 9.6 MB in-RAM vector store · 100k × 768-d · raw f32 vs PQ code (raw tier on disk)

Per-vector RAM

full f32 → PQ code

32× 3072 B → 96 B full f32 → PQ code

Recall held

recall@10 at 100k · PQ + exact rerank

1.000 recall@10 @ 100k recall@10 at 100k · PQ + exact rerank

ADC scan per query

plain PQ (O(N)) vs IVF-PQ · fewer codes is better

plain PQ 100% · 100k codes IVF-PQ 8.2% fraction of the 100k PQ codes the scan touches · IVF probes ≈√N cells, only a few
VectorsRAM (raw → PQ)compressionrecall@10search p50IVF scan
1,0003.07 → 0.10 MB32×1.0000.24 ms12.9%
10,00030.7 → 0.96 MB32×1.0000.68 ms8.0%
100,000307 → 9.6 MB32×1.0006.45 ms8.2%
Scope. Clustered 768-d vectors (the structure real embeddings have). PQ trades a little search time for a 32× smaller resident index; the ADC scan is still O(N), so past ~1M vectors you layer the §E HNSW graph on top of the compressed tier. The on-disk raw tier is read only for the top-k rerank. Reproduce: syna pq-bench --sizes 1000,10000,100000.
G

SIMD — vectorizing the hot dot-product kernel

Every vector backend — the brute-force scan, HNSW's distance, PQ's exact rerank — bottlenecks on the same dot product over f32 embeddings. We replaced the scalar loop with an AVX2+FMA kernel (runtime-detected via std::arch, scalar fallback), keeping it pure-Rust and dependency-free. Isolated, the kernel is 5.5–8.7× faster; end-to-end search shows ~2.8× because the dot is one stage of many.

SIMD kernel speedup

AVX2+FMA vs scalar dot · by vector dim

128-d 6.7× 256-d 8.7× 768-d 7.3× 1536-d 5.5× AVX2+FMA vs scalar dot · by vector dim

Per-op at 768-d

ns/op · scalar vs SIMD

scalar 286 ns AVX2+FMA 39 ns dot product at 768-d · 7.3× apart
Scope. Averaged over 2M iterations per dim with black_box to defeat dead-code elimination; 768-d is the CodeRankEmbed dimension. Reproduce: syna simd-bench.
H

Incremental write path — reindex independent of history

A one-line edit used to rebuild the whole index and re-serialize every vector — O(N) per edit, growing as a file accrues versions. Now an edit folds only its new chunks into the live index and appends them to docs.ndjson; only the small vector-free manifest is rewritten. Per-edit cost is O(changed) and independent of edit history.

Latency is flat vs history

per-edit reindex ms over 300 edits

3.1 ms reindex p50 vs edit # (1→300) · flat = history-independent

History-independent

reindex p50 · first vs last quartile

first ¼ 3.06 ms last ¼ 3.05 ms reindex p50: first vs last quartile · 1.00× (1.0 = flat)
Scope. 300 one-line edits to a single file over a fixed 200-file working set; flat ratio = last-quartile p50 ÷ first-quartile p50 ≈ 1.00. Reproduce: syna incr-bench.
I

Standard benchmark — CodeSearchNet-style

The 18-query gold set above is hand-written; this is its held-out complement. Following the CodeSearchNet method, a harness auto-harvests 186 docstring→function pairs from the real source and strips the doc comments from the indexed code, so the query text is never in the index (no leakage). Semantic embeddings win decisively on this larger, bias-free set.

Retrieval quality — 186 held-out NL→code queries

recall@k & MRR · semantic vs lexical · higher is better

0.0 0.2 0.4 0.6 0.8 1.0 0.75 0.42 R@1 0.86 0.58 R@3 0.91 0.69 R@5 0.96 0.78 R@10 0.82 0.53 MRR semantic (CodeRankEmbed) lexical (hash)
Scope. 186 auto-harvested queries, file-level gold, doc comments stripped before indexing — a CodeSearchNet-style eval over SynaFS's own source. Reproduce: python3 bench/csn_bench.py.

External repos — experimental, not yet a public win claim

private-generated multi-repo sweep · not a public leaderboard

0.0 0.1 0.2 0.3 0.4 0.5 0.23 0.15 R@1 0.38 0.18 R@5 0.45 0.20 R@10 0.29 0.17 MRR semantic (CodeRankEmbed) lexical (hash)
Scope. The external multi-repo sweep is still experimental and private-generated. The current checked-in artifact does not establish a publishable out-of-domain semantic-vs-lexical win; arcflo aggregate records recall 0.343 and MRR 0.162 without a public split. Public CoIR / CodeSearchNet leaderboard work remains future work.
J

Warm-model daemon — query latency

With the real CodeRankEmbed embedder, every cold syna query reloads the 137M-parameter model (~140 ms) before a ~36 ms search. A per-repo daemon holds the engine and its model resident; query/edit/commit/index route to it over gRPC-Web, paying the load once per session instead of once per call.

Per-query latency — cold vs warm

full syna query wall · CodeRankEmbed · lower is better

cold 263 ms warm 39 ms full syna query wall · CodeRankEmbed · lower is better

20-query session

cumulative wall over 20 queries · lower is better

cold 5.2 s warm 0.8 s cumulative wall over 20 queries · lower is better
Scope. 59-file / 1,096-chunk index, 20 queries. Cold reloads the model each call; warm routes to a resident daemon over gRPC-Web (≈35 ms RPC). The ~36 ms query-embed forward pass is the floor. Reproduce: python3 bench/daemon_bench.py.
K

Ranking priors — when the test file outranks the code it tests

Test files repeat the implementation's vocabulary almost verbatim, and RRF scores cluster within a few percent, so a well-named test regularly edged out the very code it exercises. Two priors fix this. Stage 2.5 applies a mild demotion to test-suite paths (tests/, test_*.py, *.test.js, *_test.go, conftest.py, …) — about 7 ranks at the top, enough to flip a near-tie toward the implementation, and uniform so genuinely test-seeking queries (whose competitors are also tests) keep their order. Stage 3.5 caps any one file at two chunks in the top-N, with displaced hits backfilling the tail; the failure that motivated it was a single well-named test file flooding an entire top-3 while the answer sat one rank below.

In plain terms Search results come back in an order, and the order is the whole product — an answer at position eight may as well not exist. The awkward case is that a test file and the code it tests read almost identically to a machine: same function names, same vocabulary, same concepts. So the test kept winning, and people searching for how something works landed on the code that checks it instead. The two adjustments below tilt those near-ties back toward the real implementation, gently enough that someone genuinely looking for a test still finds one.

Gold-set retrieval, before → after

recall@1 and MRR · coderank index · higher is better

R@1 before 0.522 R@1 after 0.565 MRR before 0.630 MRR after 0.659 recall@1 and MRR · coderank index · higher is better
Scope. The demotion factor was tuned from 0.95 to 0.90 after a comment-stripped private corpus where test files beat their implementations by roughly 5% — a single-query observation, not a corpus-level measurement, so treat the retune as a judgement call rather than a benchmarked delta. The public gold set is test-free, which is exactly why it is the right gate here: it confirms the prior costs nothing where it should do nothing. Reproduce: python3 experiments/harness/retrieval_gate.py --corpus <dir>. SYNA_DEMOTE_TESTS=0 and SYNA_DIVERSITY=0 opt out of each stage independently.
L

GPU embedding — 3.8× off the cold index, verified on Metal

Embedding dominates indexing wall time — on a real corpus it is the overwhelming majority of it, which makes the forward pass the only thing worth accelerating. The embedder now runs on Apple Metal behind an opt-in build feature (--features coderank-metal, SYNA_DEVICE=metal), and the win is close to the arithmetic limit of moving that pass onto the GPU. Peak memory drops too, because model tensors live in GPU-side allocations instead of the malloc heap.

In plain terms To search by meaning, every piece of code first has to be turned into a list of numbers that captures what it does — that is the “embedding”, and producing it is heavy arithmetic. A graphics card is built for exactly that kind of arithmetic, so handing the work to one makes the first indexing pass several times faster. The catch worth stating plainly: a faster answer is worthless if it is a different answer, so the results were checked against the CPU run and are identical.

Cold index wall — CPU vs Metal

355 files / 1,873 chunks · same worker settings · lower is better

CPU 411.3 s Metal 106.9 s 355 files / 1,873 chunks · same worker settings · lower is better

Peak resident memory

peak RSS during the same index · lower is better

CPU 5.5 GiB Metal 1.0 GiB peak RSS during the same index · lower is better
Same results, not just faster. A GPU path is only worth shipping if it is output-identical. Chunk and symbol counts match the CPU run exactly (1,873 / 1,056) and all five test queries return the same top-1 hit with identical scores. One sharp edge found on the way: candle 0.10's Metal backend corrupts results under concurrent forward passes on a shared device — four workers produced all-NaN embeddings — so GPU forwards are serialized behind a lock while the CPU path stays fully parallel.
Scope. Measured on an M5 Max (macOS 26.4) against a 355-file / 1,873-chunk JS corpus (fastify) that is not in this repository, so the run is not reproducible from a checkout — the table is committed in docs/embedding-decision.md §3.2 rather than as a JSON artifact. CUDA remains unverified: the coderank-cuda feature exists and is wired the same way, but there is no NVIDIA device in CI or on the dev boxes, so nobody has run it. Both GPU features are #[cfg]-gated and off by default; the tested default is still CPU, and a GPU request without the matching feature falls back to CPU with a warning.
M

Where the time actually goes — and what we did not build because of it

Every benchmark above this point uses the synthetic hash embedder, which is near-free by design — that is what isolates the index, store and search machinery. Profiling the real CodeRankEmbed model tells a much blunter story about production, and it is the single most useful measurement in this document.

In plain terms It is easy to optimize the part of a system you find interesting rather than the part that is actually slow. So before building anything, we measured where the time goes — and almost all of it turned out to sit in a single step: converting code into searchable meaning. Everything else, the parts most of this page is about, is thousands of times cheaper. The useful consequence is negative: several improvements that sounded worthwhile were measured, found to be invisible next to that one step, and deliberately not built.
Indexinghashcoderank
37 chunks~0.00 s2.6 s
346 chunks~0.001 s31 s
process RSS6 MB~1.1 GB
Embedding is ~99.9% of indexing wall time. All the index, chunk and store work the sections above optimize is sub-millisecond by comparison. That reframes the problem: indexing throughput is an embedder problem, not an index problem — GPU execution, batch shape, quantization, and the embed cache that already makes incremental edits cheap. It also means a whole category of plausible-sounding index optimizations would be unobservable in production, so we measured first and then declined to build them.

Two-level thread tuning

file workers × candle's internal threads · 346 chunks · lower is better

32 / default 23.5 s 32 / candle 1 37 s 8 / 4 44 s 16 / 2 65 s file workers × candle's internal threads · 346 chunks · lower is better

The textbook optimization that lost

naive pad-to-longest batching vs per-text · 346 chunks · lower is better

per-text 23 s batched ×4 124 s batched ×1 280 s naive pad-to-longest batching vs per-text · 346 chunks · lower is better
Losing the obvious way, then winning the specific way. Batching a group of texts into one padded forward pass is the standard transformer-throughput win, and here it lost badly — 124 s at four threads and 280 s at one, against 23 s for plain per-text encoding. The reason is specific to code: chunks vary wildly in length, attention is O(seq²), so padding everything to the longest item wastes more compute than batching saves, and it competes with the file-level parallelism already filling the cores. The fix was not to abandon batching but to attack exactly that cause — sort by length and pack groups under a batch × padded_seq² budget, so short chunks share a pass while long ones stay in small groups. That version ships and is worth ~1.48× on short-chunk batches, with a padding key-mask making every row's output bit-identical to encoding it alone.

Deferred on purpose

These were all on the list and are all recorded as declined, so the decision is explicit rather than forgotten. Each sits below the noise floor of a real embedder — building them would add risk and complexity for a gain no deployment could observe.

Incremental symbol graph
Rebuilding the graph per edit is O(working-set symbols) and takes microseconds; the one chunk that gets re-embedded costs ~75 ms. A correct incremental update is genuinely hard — call edges resolve by name, so one file's change can shift edges globally. Bad risk/reward, so only the safe part was done: reindex no longer clones the symbol set twice per edit.
mmap the vector store
It would cut index heap on open, but RSS is dominated by the ~1 GB model until the index reaches millions of chunks — 100k vectors is 307 MB raw, or 9.6 MB under PQ. Not worth the unsafe mmap plumbing at this scale.
PQ as an automatic default
Stays opt-in behind SYNA_ANN=pq. It trades exact recall for 32× memory, which only pays off past the millions-of-vectors regime; auto-gating that is a policy call best made with data from a real large index rather than presumed. The backend is built, benched, and one flag away.
Parallel candidate generation
Vector and BM25 candidate generation run sequentially, but both are sub-millisecond and the query-embedding step (~75 ms on coderank) dwarfs them. Threading two sub-millisecond stages would cost more in overhead than it saves.
Scope. Profiling numbers are from docs/performance.md on a 32-core host with CodeRankEmbed on CPU; they are committed as a document rather than as a JSON artifact, so unlike the charted sections above there is no machine-readable run to replay. The thread sweep and the batching comparison are the same 346-chunk corpus and the same measurement pass. Note the doc predates the shipped length-sorted batching and still describes only the reverted naive version — the code in crates/syna-embed/src/coderank.rs is the current truth.
N

What a session actually costs — cold start and payload

Two clusters of work, both aimed at the parts of a session that are pure overhead. Cold start is everything paid before the first useful answer — loading a 137M-parameter model, rebuilding an index structure, re-reading a tree to check for edits. Payload is what each reply carries: the same answer can arrive as pretty-printed JSON full of fields no tool reads, or as compact text.

In plain terms Everything above measures how well the search finds things. This is about the two costs that surround it: the wait before the first answer, and the size of each answer. Both matter for a different reason than speed — an assistant pays for every word it reads, so a reply padded with material it did not need is billed just like useful material. Shrinking the wait and trimming the reply are unglamorous, and together they moved the numbers more than any ranking change did.
What changedBeforeAfter
Cold start
Warm daemon instead of reloading the model per call263 ms39 ms
HNSW graph cached on disk (.syna/vindex.bin) instead of rebuilt on open250 ms50 ms
Model weights loaded lazily — graph-only sessions never pay for them5–6 s0.64 s
Drift cache remembers its verdict instead of re-reading the tree every query~140 ms/querystat-only
Query embedding on Metal instead of CPU37 ms10.5 ms
Payload
search replies as compact text, not pretty-printed JSON−59% tok
symbol_lookup drops the 64-hex id no tool ever consumes−68% tok
A batch of questions returns snippets, not N full source blocks−42% tok
Top hit's source inlined, so the answer needs no follow-up read14 ops7 ops
Scope. Same evidence class as section F, and worth the same caution: these come from internal A/Bs and profiling runs recorded in commit messages, on corpora (django, fastify) that are not in this repository, with no committed result artifact — so unlike the charted sections above, none of them can be replayed from a checkout. The mechanisms are all in the source and the environment variables that disable them are documented; the percentages are directional. The daemon row is the exception: it is backed by bench/daemon_results.json, and the charted version is section J.

Honesty notes

# build with the local semantic embedder, then run the harness
cargo build --release --features coderank
python3 bench/run_bench.py            # → bench/results.json
python3 bench/run_bench.py --skip-coderank   # lexical only, no model download

Benchmark write-up ships with the source repo as docs/benchmarks.md.