brink_runtime/bench_counters.rs
1//! Bench-only debug counters for Arc-clone / COW-copy events (issue #821
2//! Workstream B seed, `docs/runtime-bench.md`).
3//!
4//! The value model's performance claims (`docs/value-model-spec.md` §5/§6)
5//! rest on two mechanisms: sharing a collection is an O(1) `Arc::clone`, and
6//! mutating a shared collection pays exactly one O(n) copy (via
7//! `Arc::make_mut` inside `Value::array_make_mut`/`map_make_mut`/
8//! `record_make_mut`) before becoming unique again. Wall-clock benchmarks
9//! can only *infer* whether these mechanisms actually fired; these counters
10//! measure it directly.
11//!
12//! This entire module exists only when the `bench-counters` feature is
13//! enabled — it is not part of the `default` feature set, so `cargo build
14//! -p brink-runtime` (no extra flags) never compiles it in: there is no
15//! `bench_counters` module, no atomics, no call-site branches — a
16//! compile-time cut, not a runtime toggle. The call sites in
17//! `collection_ops.rs`/`record_ops.rs`/`vm.rs` that report into this module
18//! do so through tiny `note_*` wrapper functions that are themselves
19//! `#[cfg]`-gated to a no-op empty body when the feature is off, so the
20//! wrapper call inlines away to nothing (verified by the gate: `cargo build
21//! -p brink-runtime`/`cargo clippy` with no `bench-counters` feature builds
22//! clean with the module physically absent).
23
24use core::sync::atomic::{AtomicU64, Ordering};
25
26static COW_COPIES: AtomicU64 = AtomicU64::new(0);
27static ARC_CLONES: AtomicU64 = AtomicU64::new(0);
28
29/// A point-in-time read of the counters. Cheap `Copy` value so benches can
30/// snapshot before/after a measured section and diff.
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
32pub struct BenchCounters {
33 /// Number of times `array_make_mut`/`map_make_mut`/`record_make_mut`
34 /// found a shared `Arc` (`strong_count > 1`) and paid the one-time O(n)
35 /// copy — the "mutate-while-shared" cost value-model-spec §5 argues is
36 /// bounded per share, not unbounded.
37 pub cow_copies: u64,
38 /// Number of times a collection-typed `Value` (`Array`/`Map`/`Record`)
39 /// was duplicated via a cheap `Arc::clone` (an O(1) snapshot/read) —
40 /// the "sharing is O(1)" half of the same claim.
41 pub arc_clones: u64,
42}
43
44/// Record one COW copy event.
45pub fn record_cow_copy() {
46 COW_COPIES.fetch_add(1, Ordering::Relaxed);
47}
48
49/// Record one Arc-clone (cheap share) event.
50pub fn record_arc_clone() {
51 ARC_CLONES.fetch_add(1, Ordering::Relaxed);
52}
53
54/// Read the current counter values without resetting them.
55pub fn snapshot() -> BenchCounters {
56 BenchCounters {
57 cow_copies: COW_COPIES.load(Ordering::Relaxed),
58 arc_clones: ARC_CLONES.load(Ordering::Relaxed),
59 }
60}
61
62/// Zero both counters. Benches call this before the measured section so
63/// setup work (compiling, linking, building initial fixtures) doesn't
64/// pollute the count.
65pub fn reset() {
66 COW_COPIES.store(0, Ordering::Relaxed);
67 ARC_CLONES.store(0, Ordering::Relaxed);
68}
69
70#[cfg(test)]
71mod tests {
72 use super::{record_arc_clone, record_cow_copy, reset, snapshot};
73
74 /// Counters start at zero after `reset`, and each `record_*` call
75 /// increments only its own field — proves the two counters are
76 /// independent, not aliased to the same atomic.
77 #[test]
78 fn counters_are_independent_and_resettable() {
79 reset();
80 record_cow_copy();
81 record_cow_copy();
82 record_arc_clone();
83 let snap = snapshot();
84 assert_eq!(snap.cow_copies, 2);
85 assert_eq!(snap.arc_clones, 1);
86
87 reset();
88 let snap = snapshot();
89 assert_eq!(snap.cow_copies, 0);
90 assert_eq!(snap.arc_clones, 0);
91 }
92}