Skip to content

perf(resource): reduce EIP search CPU by 93%#306

Open
Savid wants to merge 1 commit into
masterfrom
perf/eip-search-hot-path
Open

perf(resource): reduce EIP search CPU by 93%#306
Savid wants to merge 1 commit into
masterfrom
perf/eip-search-hot-path

Conversation

@Savid

@Savid Savid commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Optimize the per-query hot path in EIPIndex.Search while preserving the same ranked EIPs and scores.

The cold, held-out confirmation improved ns_per_op from 1,184,777 to 78,539 ± 463: approximately 93.37% less CPU time / 15.1× throughput for the measured search workload.

What changed

  • Precompute lowercase EIP title, description, and content once in NewEIPIndex instead of lowercasing the corpus for every query.
  • Build a conservative 4096-bit character-trigram Bloom filter for each EIP. Failed probes skip the exact substring scan; successful probes still run strings.Contains, so Bloom collisions cannot change results.
  • Precompute query trigram probes, order them rarest-first, and use a build-time inverted index to visit only EIPs that contain the rarest probe bit.
  • Convert chunk embeddings to float64 once at index construction and convert each query vector once per search.
  • Inline the four-accumulator dot product in the chunk-scoring loop.
  • Replace the per-query dedup map and intermediate chunk-score allocation with a dense best-score slice.
  • Replace the full result sort with bounded top-k heap selection, followed by a typed descending sort of the selected results.
  • Gate the EIP-number regexp behind a cheap case-insensitive eip byte scan.
  • Derive result membership from the positive best-score invariant, avoiding the redundant presence slice used during optimization.

There is no public API or wire-format change.

Benchmark result

The benchmark times only EIPIndex.Search over freshly seeded, all-distinct queries. The baseline used three runs after a discarded warmup and measured 1,184,777 ns/op at approximately ±0.28% jitter. Optimization rounds used five interleaved candidate/incumbent pairs on the same VM, with a fresh seed per round.

The final artifact was then cold-booted and measured with held-out seed 1020, which was never used for selection:

Measurement ns/op Reduction vs baseline
Baseline 1,184,777
Loop best 78,692 93.36%
Cold held-out confirmation 78,539 ± 463 93.37%

Best-so-far progression

Best-so-far EIP search latency across 20 rounds

Per-round paired evidence

Per-round paired gain with standard-error whiskers

Correctness and anti-gaming controls

The experiment's frozen oracle checked:

  • identical ranked EIP IDs and scores within 1e-6;
  • concurrent use under the race detector;
  • input immutability;
  • no returned-result aliasing;
  • the repository's existing test suite;
  • changing benchmark seeds, preventing a lookup table or memo cache from winning on replayed inputs.

The promoted source initially matched the cold-confirmed snapshot byte-for-byte. The only post-promotion cleanup was deleting the now-unused predecessor filterMayContain helper after the precomputed-probe implementation replaced every call; this has no runtime behavior.

Evidence caveats

The final artifact did reproduce cold, so the end-to-end result is the number to trust. However, the experiment's accepted-gain ledger did not reconcile: it over-counted gains by 134,661 ns because round 8 compared against a stale pre-round-7 incumbent backup and re-banked most of the round-7 Bloom-filter gain. Consequently, this PR does not claim that the individual round gains add up; it claims only the independently confirmed baseline-to-final result.

Round 20's removal of the presence slice was retained as a simplicity tiebreak. Its isolated paired gain, 352.8 ± 198.8 ns, did not clear the configured 556.6 ns acceptance threshold. The aggregate artifact containing that change nevertheless passed the held-out cold confirmation and correctness oracle.

Tradeoffs

  • Index construction now does additional lowercasing, trigram indexing, and float conversion work.
  • The index retains precomputed lowercase text, Bloom filters, an inverted index, and float64 chunk vectors, increasing resident memory in exchange for substantially lower per-query CPU.
  • The benchmark targets steady-state search latency; initialization time and memory growth were not benchmarked by this experiment.

Validation

  • go test ./...
  • go test -race ./pkg/resource/...
  • golangci-lint run ./pkg/resource/...

All pass locally.

@github-actions

Copy link
Copy Markdown
Contributor

🐼 Smoke eval — c99b2b6: ✅ 8/8 pass

📊 Interactive report — tokens p50 16,595 · tokens/solve 19,498.

Reference points: master@a045b91 100% · v0.38.1 100%.

question result tokens tools
forky_node_coverage 14,938 5
tracoor_node_coverage 16,591 6
mainnet_block_arrival_p50 16,156 8
list_datasources 13,025 2
block_count_24h 16,599 9
missed_slots_24h 20,045 16
chartkit_default_arrival_distribution 36,197 12
storage_upload_session_scoped 22,432 22
🔭 Langfuse traces (8 runs; ⚠️ = failed)

The report walks this branch's commits against the master baseline and the most recent release. A self-contained copy is in the run's eval-smoke-* artifact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants