1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
//! Shared utilities and hygiene policy for the laurus benchmark suite.
//!
//! This module is included by every bench file via `mod common;` so the suite
//! shares a single source of truth for deterministic randomness, sample sizes,
//! and the contract every bench is expected to follow.
//!
//! # Hygiene rules (apply to every bench file in this directory)
//!
//! 1. **Deterministic input**: never use `rand::rng()` (OS-seeded). Use the
//! LCG helpers in this module so two consecutive runs produce comparable
//! numbers.
//! 2. **File-level doc comment**: each bench file starts with a `//!` block
//! listing scope, scenarios, how to run, and how to filter.
//! 3. **One-time sanity assert**: each top-level bench function calls the
//! measured code once before `b.iter` and asserts on the shape of the
//! result (e.g. result count > 0). This catches regressions that produce
//! empty output without affecting the timed loop.
//! 4. **`sample_size` policy**: use [`SAMPLE_SIZE_FAST`] for cheap operations
//! (sub-50 ms per iter) and [`SAMPLE_SIZE_SLOW`] for slow construction
//! paths. Pick one of the two; do not invent intermediate values.
//!
//! # Recommended environment
//!
//! Keep `cargo bench` as the default invocation; the bumped
//! [`SAMPLE_SIZE_SLOW`] (30 samples — Criterion's documented minimum for a
//! reliable t-test) is what brings the within-run interquartile spread on
//! `topk_or_skewed_tf/should_or_topk10/100000` down from "noise dominates"
//! to roughly ±1 % of the median. That is enough for `--baseline` runs to
//! tell real changes from jitter on a typical desktop without further
//! intervention.
//!
//! For micro-isolation (e.g. tracking a single hot loop where a 2-3 % win is
//! the headline), the optional wrapper at `scripts/bench-stable.sh` pins the
//! cargo bench process to one CPU and raises its priority. Caveat: pinning
//! constrains any parallel work the bench fixture does to the chosen core,
//! so the absolute timings shift relative to unpinned runs and historical
//! baselines saved without pinning are not directly comparable. Use it only
//! when the extra control is worth losing that comparability — for the
//! perf-PR style baselines this suite is built around, plain
//! `cargo bench` is the right tool.
//!
//! Other knobs that help when even that isn't enough: set the CPU governor
//! to `performance`, disable turbo boost (so the chip can't drop a sample
//! into a thermally-throttled window), and stop browsers / IDEs before
//! kicking off a comparison run.
//!
//! # Suppress unused warnings
//!
//! Each bench is its own compile unit and may use only a subset of these
//! helpers. `#![allow(dead_code)]` keeps clippy quiet across the suite.
use Arc;
use FileStorageConfig;
use MemoryStorageConfig;
use ;
/// Default seed for deterministic LCG state. Used as the starting point in
/// every bench so two runs of `cargo bench` produce identical inputs.
pub const DEFAULT_SEED: u64 = 0xDEAD_BEEF_CAFE_F00D;
/// `sample_size` for fast operations (search, distance, scoring loops). This
/// is Criterion's default; spell it out explicitly so the policy is visible
/// at the call site.
pub const SAMPLE_SIZE_FAST: usize = 100;
/// `sample_size` for slow construction paths (HNSW build, IVF training,
/// engine population at large scale, top-K queries on ≥ 10k corpora).
/// Sized at 30 — Criterion's documented minimum for a stable t-test, which
/// is what gates the "performance has improved / regressed" decision in
/// `--baseline` runs. Lower values (the previous 10) make the reported
/// change percentage flip sign across two runs of identical code at the
/// larger sizes, which makes perf PRs hard to evaluate honestly.
pub const SAMPLE_SIZE_SLOW: usize = 30;
/// Inline LCG (numerical recipes constants) advancing `state` by one step
/// and returning a deterministic value in `[0, 1000)`.
///
/// Used in places where the bench wants drop-in replacement for the existing
/// `[0, 1000)` data range (e.g. BKD point coordinates).
/// Select the storage backend for a bench based on `LAURUS_BENCH_DISK`.
///
/// - **Default (env unset)**: returns an in-memory storage. This is what
/// every bench used before #444 and is the right choice for
/// microbenchmarks that want to remove I/O variance.
/// - **`LAURUS_BENCH_DISK=1`**: returns a file-backed storage rooted in a
/// freshly-created temp directory. Each call yields a distinct
/// directory so concurrent benches do not collide.
///
/// The temp directory created in the disk-backed branch is intentionally
/// **leaked** — `tempfile::TempDir::keep` keeps it on disk after the
/// returned `Storage` is dropped. Bench runs accumulate a handful of
/// directories under `$TMPDIR` and rely on OS-level `/tmp` cleanup
/// (`systemd-tmpfiles`, reboots, etc.) rather than per-iteration cleanup.
/// This is acceptable for benchmark runs and avoids the lifetime
/// gymnastics of returning `(Storage, TempDir)` everywhere.
///
/// Disk numbers are sensitive to the host filesystem and OS page cache.
/// They are useful for **comparing** in-tree changes (e.g. before / after
/// a perf PR), not for absolute throughput claims. Document this caveat
/// in any PR that posts numbers from `LAURUS_BENCH_DISK=1`.
/// Inline LCG variant returning a deterministic `f32` in `[0, 1)`.
///
/// Suited to vector-component generation (cosine / dot-product workloads
/// expect bounded magnitudes; the `[0, 1)` range mirrors what `rand::rng()`
/// previously produced via the `Rng::random::<f32>()` call).
/// Generate a deterministic `Vec<f32>` of length `dim` advancing the
/// caller-provided LCG state. Components are in `[0, 1)`.
// ============================================================================
// .fvecs loader (Issue #498 real ANN benchmark data)
// ============================================================================
/// Read vectors from a `.fvecs` file (TEXMEX format used by SIFT1M /
/// GIST / GloVe distributions). Each vector record on disk is laid out
/// as:
///
/// ```text
/// [dim: u32 LE] 4 bytes
/// [values: f32 LE × dim] dim * 4 bytes
/// ```
///
/// # Arguments
///
/// * `path` - File path to the `.fvecs` file.
/// * `expect_dim` - Required dimension; the function panics if any
/// record disagrees so a corrupted fixture fails loudly.
/// * `max` - Optional cap on the number of vectors to read; useful for
/// sub-sampling (e.g. the SIFT1M-50k case is `max = Some(50_000)`).
///
/// # Returns
///
/// A `Vec<Vec<f32>>` of length up to `max` (or the full file).
/// L2-normalise a vector in place so Cosine distance is well-defined.
/// SIFT vectors are non-negative integer histograms (norms > 0); this
/// rescales them onto the unit hypersphere to match the distribution
/// the Cosine searcher assumes.
/// Repo-relative path to the `.cache/sift/` directory populated by
/// `scripts/fetch-sift.sh`. The path is anchored at this crate's
/// manifest dir so it works from `cargo test`, `cargo bench`, or any
/// other workspace member's working directory.
// ============================================================================
// Cold-cache bench harness
// ============================================================================
//
// Cold-cache benches simulate "first access from disk" by evicting the
// OS page cache for an index file before each measured iteration.
// Without eviction, the kernel keeps the file warm in the page cache
// after construction (or after the previous sample), so any read into
// the same byte range hits RAM and the measurement collapses to a
// memcpy benchmark.
//
// On Linux, `posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)` is the
// canonical knob for this — it requests that the kernel drop the
// file's clean pages from the page cache. It is non-destructive
// (dirty pages stay), needs no root, and is precisely scoped to the
// file being benchmarked.
//
// Other operating systems do not expose an equivalent at this level
// (macOS' `F_NOCACHE` applies to a fd's future I/O rather than evicting
// existing pages; Windows has no public API for it). On those hosts
// the helpers below are best-effort no-ops and the cold-cache numbers
// will silently conflate with warm-cache state — call this out
// explicitly in any PR that posts numbers measured off Linux.
/// Evict the OS page cache for a single file. Best-effort: real eviction
/// on Linux via `posix_fadvise(POSIX_FADV_DONTNEED)`; no-op on other
/// hosts.
///
/// # Arguments
///
/// * `path` - Absolute or relative path of the file to evict.
///
/// # Errors
///
/// Returns the underlying `std::io::Error` if the file cannot be
/// opened or the syscall fails.
/// Evict the OS page cache for every regular file under `dir`,
/// recursively. Used by cold-cache benches that touch a whole
/// segment directory (`*.dict`, `*.post`, `*.docs`, etc. are all
/// flushed in one call).
///
/// # Arguments
///
/// * `dir` - Directory to walk recursively.
///
/// # Errors
///
/// Returns on the first error from either `read_dir` or
/// [`evict_file_cache`]. Partial eviction may have already happened
/// when the error is returned; for cold-cache bench correctness, treat
/// any error from this helper as "unable to guarantee cold cache" and
/// abort the bench.
/// Returns `true` if the host supports real page-cache eviction via
/// the helpers above. Cold-cache benches should consult this and emit
/// a one-line warning when running on a host where eviction is a
/// no-op, so PR readers don't trust the resulting numbers as
/// "cold-cache" silently.
pub const