Skip to content

Commit a6541db

Browse files
authored
Merge pull request #357 from AdaWorldAPI/ccr-0455e606-wmtsor
key16 probe: time each arm interleaved with arm A
2 parents ba9326d + ba65e66 commit a6541db

1 file changed

Lines changed: 113 additions & 30 deletions

File tree

‎examples/key16_match_probe.rs‎

Lines changed: 113 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,51 @@
3333
//! env -u RUSTFLAGS cargo --config .cargo/config-v4.toml run --release --example key16_match_probe
3434
//! ```
3535
//!
36+
//! ## Timing method
37+
//!
38+
//! Each alternative arm is timed INTERLEAVED with arm A: every round runs both
39+
//! once in each order (A-then-B, then B-then-A), and `A/B` / `A/C` is the
40+
//! median over observations of the geometric mean of the two per-round
41+
//! ratios (`paired_ns`). Drift on a shared machine then
42+
//! lands on both arms of a round instead of on one arm's whole block of runs.
43+
//! The PROVISIONAL tables below predate this (each arm was timed in its own
44+
//! block of 101 runs, and the ratio was a ratio of medians); they are kept as
45+
//! measured and are not comparable row for row with a re-run.
46+
//!
47+
//! ## Interleaved pass (PROVISIONAL)
48+
//!
49+
//! Measured 2026-10-11 with `paired_ns` (both execution orders per
50+
//! observation), same 4-core VM, rustc 1.99.0, NO other build running (load
51+
//! average 0.3 to 0.5). Still a shared virtual machine, so still PROVISIONAL,
52+
//! not the quiet-host run the W3 / W4 decision waits for. Median ratios
53+
//! (A ns / arm ns), one pass per tier:
54+
//!
55+
//! | N | stride | sel | v3 A/B | v4 A/B | v4 A/C |
56+
//! |---|---|---|---|---|---|
57+
//! | 65 536 | 16 | 1 % | 2.45 | 3.12 | - |
58+
//! | 65 536 | 16 | 50 % | 2.44 | 3.09 | - |
59+
//! | 65 536 | 32 | 1 % | - | - | 2.58 |
60+
//! | 65 536 | 32 | 50 % | - | - | 2.62 |
61+
//! | 65 536 | 512 | 1 % | - | - | 1.07 |
62+
//! | 65 536 | 512 | 50 % | - | - | 1.04 |
63+
//! | 1 048 576 | 16 | 1 % | 1.74 | 1.92 | - |
64+
//! | 1 048 576 | 16 | 50 % | 1.72 | 1.97 | - |
65+
//! | 1 048 576 | 32 | 1 % | - | - | 1.64 |
66+
//! | 1 048 576 | 32 | 50 % | - | - | 1.63 |
67+
//! | 1 048 576 | 512 | 1 % | - | - | 1.27 |
68+
//! | 1 048 576 | 512 | 50 % | - | - | 1.22 |
69+
//!
70+
//! Against the earlier passes: the v4 arm B ratios at 65 536 rows are HIGHER
71+
//! (3.12 / 3.09 against 2.13 to 2.91), the v4 arm B 1 048 576-row 50 % ratio is
72+
//! lower (1.97 against 2.19 / 2.21), and the v3 ratios sit near the top of the
73+
//! earlier range. Arm B wins at stride 16 on both tiers and arm C wins at
74+
//! stride 32, as before. One earlier pattern did NOT survive: at stride 512
75+
//! with 65 536 rows arm C is now slightly ABOVE 1 (1.07 / 1.04), where both
76+
//! earlier passes had it at or below 1 (0.92 to 1.00). A pass measured just
77+
//! before this one, which ran A first in 51 of 101 rounds, again gave 0.94 /
78+
//! 0.95. So whether the gather path pays at that size is not settled; it sits
79+
//! within the spread between passes.
80+
//!
3681
//! ## PROVISIONAL results
3782
//!
3883
//! **PROVISIONAL.** Measured 2026-10-11 on a 4-core Intel Xeon @ 2.10 GHz
@@ -233,19 +278,44 @@ fn build(n: usize, stride: usize, p: f64, pattern: &[u8; 16], care: &[u8; 16], s
233278
bytes
234279
}
235280

236-
fn median_ns(mut f: impl FnMut()) -> f64 {
281+
/// Time `a` and `b` INTERLEAVED: each observation is one A-then-B round and
282+
/// one B-then-A round, back to back, so slow drift on a shared machine (another
283+
/// build starting, a CPU frequency step) lands on both arms of the same
284+
/// observation instead of on whichever arm happened to be timed during it, and
285+
/// both execution orders weigh exactly equally in every observation (with an
286+
/// odd number of single rounds, one order would hold the median). An
287+
/// observation's arm time is the mean of its two runs and its ratio is the
288+
/// geometric mean of the two per-round ratios. Returns the median of each arm
289+
/// and the median ratio `a / b`; the ratio is the decision statistic, the two
290+
/// medians are context.
291+
fn paired_ns(mut a: impl FnMut(), mut b: impl FnMut()) -> (f64, f64, f64) {
237292
for _ in 0..WARMUP {
293+
a();
294+
b();
295+
}
296+
let time = |f: &mut dyn FnMut()| {
297+
let t = Instant::now();
238298
f();
299+
t.elapsed().as_nanos() as f64
300+
};
301+
let mut ta = Vec::with_capacity(REPS);
302+
let mut tb = Vec::with_capacity(REPS);
303+
let mut ratios = Vec::with_capacity(REPS);
304+
for _ in 0..REPS {
305+
let a1 = time(&mut a);
306+
let b1 = time(&mut b);
307+
let b2 = time(&mut b);
308+
let a2 = time(&mut a);
309+
ta.push((a1 + a2) / 2.0);
310+
tb.push((b1 + b2) / 2.0);
311+
ratios.push(((a1 / b1) * (a2 / b2)).sqrt());
239312
}
240-
let mut ts: Vec<f64> = (0..REPS)
241-
.map(|_| {
242-
let t = Instant::now();
243-
f();
244-
t.elapsed().as_nanos() as f64
245-
})
246-
.collect();
247-
ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
248-
ts[REPS / 2]
313+
(median(&mut ta), median(&mut tb), median(&mut ratios))
314+
}
315+
316+
fn median(v: &mut [f64]) -> f64 {
317+
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
318+
v[v.len() / 2]
249319
}
250320

251321
fn main() {
@@ -303,25 +373,38 @@ fn main() {
303373

304374
drop(shifted);
305375

306-
let mut out = vec![0u64; wlen];
307-
let a_ns = median_ns(|| {
308-
ternary_match_strided16_to_mask(black_box(&bytes), 0, stride, n, &pattern, &care, &mut out);
309-
black_box(&out);
310-
});
311-
let b_ns = (stride == 16).then(|| {
312-
median_ns(|| {
313-
arm_b(black_box(&bytes), 0, n, &pattern, &care, &mut out);
314-
black_box(&out);
315-
})
316-
});
317-
let c_ns = (stride > 16 && C_AVAILABLE).then(|| {
318-
median_ns(|| {
319-
arm_c(black_box(&bytes), 0, stride, n, &pattern, &care, &mut out);
320-
black_box(&out);
321-
})
322-
});
376+
let mut out_a = vec![0u64; wlen];
377+
let mut out_x = vec![0u64; wlen];
378+
let run_a = |out: &mut [u64]| {
379+
ternary_match_strided16_to_mask(black_box(&bytes), 0, stride, n, &pattern, &care, out);
380+
black_box(&*out);
381+
};
382+
// Each alternative is timed interleaved with its own run of arm
383+
// A; arm A alone is timed only where no alternative applies.
384+
let (a_ns, b_ns, c_ns, b_ratio, c_ratio) = if stride == 16 {
385+
let (a, b, r) = paired_ns(
386+
|| run_a(&mut out_a),
387+
|| {
388+
arm_b(black_box(&bytes), 0, n, &pattern, &care, &mut out_x);
389+
black_box(&out_x);
390+
},
391+
);
392+
(a, Some(b), None, Some(r), None)
393+
} else if C_AVAILABLE {
394+
let (a, c, r) = paired_ns(
395+
|| run_a(&mut out_a),
396+
|| {
397+
arm_c(black_box(&bytes), 0, stride, n, &pattern, &care, &mut out_x);
398+
black_box(&out_x);
399+
},
400+
);
401+
(a, None, Some(c), None, Some(r))
402+
} else {
403+
let (a, _, _) = paired_ns(|| run_a(&mut out_a), || run_a(&mut out_x));
404+
(a, None, None, None, None)
405+
};
323406
let fmt = |v: Option<f64>| v.map_or("-".to_string(), |x| format!("{x:.0}"));
324-
let ratio = |v: Option<f64>| v.map_or("-".to_string(), |x| format!("{:.2}", a_ns / x));
407+
let ratio = |v: Option<f64>| v.map_or("-".to_string(), |x| format!("{x:.2}"));
325408
println!(
326409
"{:>9} {:>6} {:>4.0}% | {:>10.0} {:>10} {:>10} | {:>7} {:>7}",
327410
n,
@@ -330,8 +413,8 @@ fn main() {
330413
a_ns,
331414
fmt(b_ns),
332415
fmt(c_ns),
333-
ratio(b_ns),
334-
ratio(c_ns)
416+
ratio(b_ratio),
417+
ratio(c_ratio)
335418
);
336419
}
337420
}

0 commit comments

Comments
 (0)