feat(bench): phase 4 — run-queue bench harness + shootout driver
- benches/rq_micro.rs: raw-structure microbench, threads x producer:consumer ratio sweep. Benches all three queue types in one binary (they compile in every build; only the runtime alias is feature-selected), so no rebuild dance. Queues sized to the op count so the occupancy contract is met. - benches/rq_runtime.rs: whole-runtime benches with the selected variant: yield-storm (pure queue churn), ping-pong-pairs (park/unpark latency), spawn-storm (slab + free list + queue under churn). Scheduler-count sweep. - scripts/bench_rq.sh: rebuilds rq_runtime per rq-* feature, runs rq_micro once, aggregates RQCSV lines into bench_results/summary.csv. - All knobs via SMARM_BENCH_* env vars; house table format + machine lines. - run_queue module is now #[doc(hidden)] pub (types + push/pop/len + MpmcRing::with_capacity) solely so the external bench binary can drive the raw structures. docs(roadmap): phase 4 ticked (harness done; numbers from the 20-core box). New fast-follow per review: assert the invariants we lean on — debug_assert! on hot paths, loud assert!/panic on cold ones, at the point of reliance; sweep existing code during the phase-5 audit, adopt as house style. Validated end-to-end at smoke scale on the 1-core sandbox: full driver run, 24-row summary.csv across micro (3 structures x sweeps) and runtime (3 variants x 3 benches x thread sweep).
This commit is contained in:
+2
-1
@@ -27,7 +27,8 @@ pub mod link;
|
||||
pub mod gen_server;
|
||||
pub mod runtime;
|
||||
pub(crate) mod raw_mutex;
|
||||
pub(crate) mod run_queue;
|
||||
#[doc(hidden)] // pub only so benches/rq_micro.rs can drive the raw structures
|
||||
pub mod run_queue;
|
||||
pub mod trace;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+18
-16
@@ -94,13 +94,13 @@ fn assert_no_preempt() {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct MutexQueue {
|
||||
pub struct MutexQueue {
|
||||
q: std::sync::Mutex<std::collections::VecDeque<Pid>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl MutexQueue {
|
||||
pub(crate) fn new(_threads: usize, max_actors: usize) -> Self {
|
||||
pub fn new(_threads: usize, max_actors: usize) -> Self {
|
||||
Self {
|
||||
// Pre-size: the queue can never outgrow the slab, and one
|
||||
// allocation at init beats reallocating under the lock later.
|
||||
@@ -108,17 +108,17 @@ impl MutexQueue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push(&self, pid: Pid) {
|
||||
pub fn push(&self, pid: Pid) {
|
||||
assert_no_preempt();
|
||||
self.q.lock().unwrap().push_back(pid);
|
||||
}
|
||||
|
||||
pub(crate) fn pop(&self) -> Option<Pid> {
|
||||
pub fn pop(&self) -> Option<Pid> {
|
||||
assert_no_preempt();
|
||||
self.q.lock().unwrap().pop_front()
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> u64 {
|
||||
pub fn len(&self) -> u64 {
|
||||
self.q.lock().unwrap().len() as u64
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ struct Cell {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct MpmcRing {
|
||||
pub struct MpmcRing {
|
||||
buf: Box<[Cell]>,
|
||||
mask: usize,
|
||||
enqueue_pos: CachePadded<AtomicUsize>,
|
||||
@@ -159,11 +159,13 @@ unsafe impl Sync for MpmcRing {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl MpmcRing {
|
||||
pub(crate) fn new(_threads: usize, max_actors: usize) -> Self {
|
||||
pub fn new(_threads: usize, max_actors: usize) -> Self {
|
||||
Self::with_capacity(max_actors)
|
||||
}
|
||||
|
||||
fn with_capacity(min_cap: usize) -> Self {
|
||||
/// Pub for the raw-structure microbench (sized to the op count so the
|
||||
/// occupancy contract is trivially met there). Runtime code uses `new`.
|
||||
pub fn with_capacity(min_cap: usize) -> Self {
|
||||
// Occupancy ≤ max_actors (queue contract), so capacity = the next
|
||||
// power of two ≥ max_actors can never overflow. (≥ 2 so mask works.)
|
||||
let cap = min_cap.next_power_of_two().max(2);
|
||||
@@ -181,7 +183,7 @@ impl MpmcRing {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push(&self, pid: Pid) {
|
||||
pub fn push(&self, pid: Pid) {
|
||||
assert_no_preempt();
|
||||
assert!(
|
||||
self.try_push(pid),
|
||||
@@ -220,7 +222,7 @@ impl MpmcRing {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop(&self) -> Option<Pid> {
|
||||
pub fn pop(&self) -> Option<Pid> {
|
||||
assert_no_preempt();
|
||||
let mut pos = self.dequeue_pos.0.load(Ordering::Relaxed);
|
||||
loop {
|
||||
@@ -252,7 +254,7 @@ impl MpmcRing {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> u64 {
|
||||
pub fn len(&self) -> u64 {
|
||||
let e = self.enqueue_pos.0.load(Ordering::Relaxed);
|
||||
let d = self.dequeue_pos.0.load(Ordering::Relaxed);
|
||||
e.saturating_sub(d) as u64
|
||||
@@ -276,7 +278,7 @@ impl MpmcRing {
|
||||
// terminates (in practice on the first stripe).
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct StripedRing {
|
||||
pub struct StripedRing {
|
||||
stripes: Box<[MpmcRing]>,
|
||||
/// Stripe count minus one (count is a power of two).
|
||||
stripe_mask: usize,
|
||||
@@ -286,7 +288,7 @@ pub(crate) struct StripedRing {
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl StripedRing {
|
||||
pub(crate) fn new(threads: usize, max_actors: usize) -> Self {
|
||||
pub fn new(threads: usize, max_actors: usize) -> Self {
|
||||
// One stripe per scheduler thread, rounded up to a power of two —
|
||||
// more stripes than threads buys nothing (at most `threads` ops are
|
||||
// in flight) and costs pop-probe latency when mostly empty.
|
||||
@@ -304,7 +306,7 @@ impl StripedRing {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push(&self, pid: Pid) {
|
||||
pub fn push(&self, pid: Pid) {
|
||||
assert_no_preempt();
|
||||
let home = self.push_ticket.0.fetch_add(1, Ordering::Relaxed);
|
||||
// Probe from the home stripe; capacity headroom (Σ ≥ 2×occupancy)
|
||||
@@ -322,7 +324,7 @@ impl StripedRing {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop(&self) -> Option<Pid> {
|
||||
pub fn pop(&self) -> Option<Pid> {
|
||||
assert_no_preempt();
|
||||
let home = self.pop_ticket.0.fetch_add(1, Ordering::Relaxed);
|
||||
for i in 0..=self.stripe_mask {
|
||||
@@ -333,7 +335,7 @@ impl StripedRing {
|
||||
None // snapshot miss possible across stripes; idle-retry absorbs it
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> u64 {
|
||||
pub fn len(&self) -> u64 {
|
||||
self.stripes.iter().map(|s| s.len()).sum()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user