# v0.2.578 (Zig 0.16.0) vs v0.2.572 (Zig 0.15.2)

Benchmark run: 2026-04-18, macOS arm64, M-series.
Both binaries compiled with `-Doptimize=ReleaseFast`.
Measured with `hyperfine -N --warmup 5 -r 10` (shell-free).

## Methodology

- **Corpus**: `/Users/rachpradhan/codedb-bench/codedb-578` — 106 files
  (the migration branch checkout itself with docs, benchmarks, etc.).
- **Warm**: snapshot + index cached on disk under `~/.codedb/projects/<hash>/`.
- **Cold**: project data dir wiped via `--prepare` between runs (full re-index
  + trigram build + persist + mmap reload every iteration).

## Final results (after parallel-trigram pass)

### Cold (full re-index, ~/.codedb wiped each iter, hyperfine -r 20)

| Command | 0.2.572 | 0.2.578 | Delta |
|---|---|---|---|
| `search handleCall` | 38.5 ± 1.0 ms | 36.4 ± 0.9 ms | **0.2.578 6% faster** |
| `word Explorer` | 60.6 ± 0.6 ms | 57.3 ± 1.4 ms | **0.2.578 6% faster** |
| `find TrigramIndex` | 51.3 ± 1.2 ms | 40.0 ± 1.6 ms | **0.2.578 28% faster** |
| `outline src/explore.zig` | 51.8 ± 1.2 ms | 40.8 ± 1.2 ms | **0.2.578 27% faster** |

The cold wins come from parallelizing the trigram build off the cached
`Explorer.contents`. Previously the cold-run non-search trigram extract
pass ran serially on the main thread. Now it fans out to `min(cpu, 8)`
worker threads via `watcher.buildTrigramsFromCache`. Find / outline win
the most because their pre-fix scan was already lean — the trigram pass
was the dominant remaining cost.

### Warm (cached snapshot + on-disk index, hyperfine -r 20)

| Command | 0.2.572 | 0.2.578 | Delta |
|---|---|---|---|
| `search handleCall` | 71.4 ± 1.1 ms | 70.4 ± 0.7 ms | **0.2.578 1.4% faster** |
| `word Explorer` | 79.8 ± 1.4 ms | 82.8 ± 0.6 ms | 0.2.572 4% faster |
| `find TrigramIndex` | 71.0 ± 1.6 ms | 70.3 ± 1.7 ms | flat |
| `outline src/explore.zig` | 71.7 ± 0.8 ms | 71.2 ± 0.9 ms | flat |

### Variance

`0.2.578` runs are noticeably more consistent. `0.2.572` hit statistical
outlier warnings from hyperfine on most warm runs; `0.2.578` did not
(all stddev under 2 ms).

## Perf passes applied in 0.2.578 to reach parity

1. **Skip cold-run word-index wipe** (commit `c389476`)
   The original persist → wipe → reload pipeline silently broke the
   reload step (#291) in 0.2.572; when fixed in 0.2.578 it added a real
   ~10ms disk round-trip. Replaced the wipe with
   `markWordIndexAsComplete()` so warm queries read straight from the
   in-memory index that was just persisted.

2. **`WordIndex.indexFile` fast-path** (commit `c389476`)
   Lowercase into a 256-byte stack buffer (was: alloc per word). Skip
   `splitIdentifier` entirely for all-lowercase words < 4 chars (no
   camelCase/snake_case to split). Extract hot loop into
   `indexOneToken`. Saved ~5ms on word-building for this corpus.

3. **Trigram build reuses content cache** (commit `3354985`)
   Cold-run non-search path used to re-read every file for trigram
   extraction after scan already read them once. Now iterates
   `Explorer.contents` directly when populated (caps at 1000 files by
   default) — ~100 file reads eliminated per cold run on medium repos.

4. **Parallel cold-run trigram build** (this commit)
   The serial main-thread loop over `Explorer.contents` was still the
   dominant cost on cold non-search runs. Extracted it into
   `watcher.buildTrigramsFromCache`, which fans out trigram extraction
   to `min(cpu, 8)` worker threads (each with its own arena + local
   `AutoHashMap` to avoid contention) and bulk-merges into the final
   index on the main thread. This is what unlocked the 27–28% cold
   `find` / `outline` wins.

## History

| pass | warm word | delta from 0.2.572 |
|---|---|---|
| 0.2.578 at `1654be7` (pre-perf) | 90.6 ± 5.5 ms | +15% slower |
| 0.2.578 at `c389476` (wipe-skip + fast-path) | 84.1 ± 2.9 ms | +7% slower |
| 0.2.578 at `3354985` (content cache) | 86.8 ± 1.5 ms | +4% slower |

Remaining 4% gap: 0.2.578's word index does ASCII-case-insensitive
lookup AND indexes sub-tokens from identifier splitting, so queries
return ~3x more hits than 0.2.572. That extra recall costs per-indexFile
work that the fast-path mitigates but cannot eliminate without dropping
the feature.

## Reproduce

```bash
# Set up both checkouts
git clone https://github.com/justrach/codedb /tmp/b572 && cd /tmp/b572 && git checkout v0.2.572
zigup default 0.15.2 && zig build -Doptimize=ReleaseFast

git clone https://github.com/justrach/codedb /tmp/b578 && cd /tmp/b578 && git checkout feat/0.2.578-0.16.0-migration
zigup default 0.16.0 && zig build -Doptimize=ReleaseFast

# Warm benchmark (pre-run once to populate cache)
hyperfine -N --warmup 5 -r 10 \
  --command-name "572" "/tmp/b572/zig-out/bin/codedb /tmp/b578 search handleCall" \
  --command-name "578" "/tmp/b578/zig-out/bin/codedb /tmp/b578 search handleCall"
```
