feat(causal): native causal profiling behind smarm-causal (RFC 007 v1)
causal_site! scoped site guards per actor slot, progress! throughput points, and a Coz-style virtual-speedup engine hooked into maybe_preempt's amortized cold block: target-site samples grow a global delay ledger; bystanders spin-absorb their debt at the next causal check, with timeslice extension so injected delay is not charged against the slice. Resume credit (Coz's blocked-thread rule) is gated on a causal_parked slot bit set only by a real park: crediting on every resume made any yield-cadence actor delay-immune and every experiment inert (found live on a 24-core run — dead-flat deltas across all sites). Report normalization uses a measured TSC frequency (~50ms calibration on first use) instead of the crate-wide 3 GHz assumption, which uniformly inflated impact numbers on a 3.7 GHz box. impact_pct() is the machine-readable form of the summary for programmatic checks. examples/causal_pipeline.rs burns fixed *work* (calibrated LCG loop), not fixed wall time — a timed busy-wait absorbs injected delay into its own budget and reads as a no-op. Self-checking: exits nonzero if causal separation fails; skips the verdict below 4 cores. Validated on a 24-core box: reserve (true bottleneck) +29.3%@25/+83.5%@50; serialize and background-compaction ~0%. Known v1 gaps (jar): timer-heap deadlines unshifted, no-check!/no-alloc actors undelayable, multi-scheduler coherence best-effort Relaxed, off-CPU blame punted, Instant::now() uncorrected. Zero-cost with the feature off; clippy -D warnings clean both ways; full suite green with and without smarm-causal.
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
//! Diagnostic probe for RFC 007 on a target box. Measures, in order:
|
||||
//! 1. TSC frequency against `Instant` (the crate assumes 3 GHz).
|
||||
//! 2. TSC sanity under actor migration: distribution of wall time actually
|
||||
//! spent in `burn_us(400)` across many runs — a bimodal/short tail means
|
||||
//! cross-core TSC offsets are cutting burns short.
|
||||
//! 3. Pipeline stage rates with no experiment running (who is the real
|
||||
//! bottleneck?).
|
||||
//! 4. The same rates during a 50% experiment on `background-compaction`
|
||||
//! (a correct implementation must slow every stage; an off-critical-path
|
||||
//! target must reduce end-to-end throughput proportionally).
|
||||
//!
|
||||
//! Run: cargo run --release --example causal_probe --features smarm-causal
|
||||
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn burn_us(us: u64) {
|
||||
let cycles = us * 3_000;
|
||||
let start = smarm::preempt::rdtsc();
|
||||
while smarm::preempt::rdtsc().saturating_sub(start) < cycles {
|
||||
smarm::check!();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// 1. TSC calibration (plain OS thread, before the runtime starts).
|
||||
let c0 = smarm::preempt::rdtsc();
|
||||
let t0 = Instant::now();
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
let hz = (smarm::preempt::rdtsc() - c0) as f64 / t0.elapsed().as_secs_f64();
|
||||
println!("tsc_hz: {:.3e} (crate assumes 3.0e9)", hz);
|
||||
|
||||
smarm::init(smarm::Config::default()).run(move || {
|
||||
// 2. burn_us(400) wall-time distribution inside a migrating actor.
|
||||
let h = smarm::spawn(|| {
|
||||
let mut samples: Vec<u64> = (0..500)
|
||||
.map(|_| {
|
||||
let t = Instant::now();
|
||||
burn_us(400);
|
||||
t.elapsed().as_micros() as u64
|
||||
})
|
||||
.collect();
|
||||
samples.sort_unstable();
|
||||
println!(
|
||||
"burn_us(400) wall us: min {} p10 {} p50 {} p90 {} max {}",
|
||||
samples[0], samples[50], samples[250], samples[450], samples[499]
|
||||
);
|
||||
});
|
||||
h.join().unwrap();
|
||||
|
||||
// 3+4. Pipeline with per-stage counters.
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let produced = Arc::new(AtomicU64::new(0));
|
||||
let reserved = Arc::new(AtomicU64::new(0));
|
||||
let notified = Arc::new(AtomicU64::new(0));
|
||||
|
||||
let (tx_ab, rx_ab) = smarm::channel::<u64>();
|
||||
let (tx_bc, rx_bc) = smarm::channel::<u64>();
|
||||
|
||||
let stop_p = stop.clone();
|
||||
let produced2 = produced.clone();
|
||||
let producer = smarm::spawn(move || {
|
||||
let mut i = 0u64;
|
||||
while !stop_p.load(Ordering::Relaxed) {
|
||||
{
|
||||
let _g = smarm::causal_site!("serialize");
|
||||
burn_us(200);
|
||||
}
|
||||
if tx_ab.send(i).is_err() {
|
||||
break;
|
||||
}
|
||||
produced2.fetch_add(1, Ordering::Relaxed);
|
||||
i += 1;
|
||||
}
|
||||
});
|
||||
|
||||
let reserved2 = reserved.clone();
|
||||
let reserve = smarm::spawn(move || {
|
||||
while let Ok(item) = rx_ab.recv() {
|
||||
{
|
||||
let _g = smarm::causal_site!("reserve");
|
||||
burn_us(400);
|
||||
}
|
||||
reserved2.fetch_add(1, Ordering::Relaxed);
|
||||
if tx_bc.send(item).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let notified2 = notified.clone();
|
||||
let notify = smarm::spawn(move || {
|
||||
while rx_bc.recv().is_ok() {
|
||||
{
|
||||
let _g = smarm::causal_site!("notify");
|
||||
burn_us(50);
|
||||
}
|
||||
notified2.fetch_add(1, Ordering::Relaxed);
|
||||
smarm::progress!("orders-processed");
|
||||
}
|
||||
});
|
||||
|
||||
let stop_bg = stop.clone();
|
||||
let background = smarm::spawn(move || {
|
||||
while !stop_bg.load(Ordering::Relaxed) {
|
||||
let _g = smarm::causal_site!("background-compaction");
|
||||
burn_us(500);
|
||||
}
|
||||
});
|
||||
|
||||
smarm::sleep(Duration::from_millis(300));
|
||||
|
||||
let window = |label: &str| {
|
||||
let (p0, r0, n0) = (
|
||||
produced.load(Ordering::Relaxed),
|
||||
reserved.load(Ordering::Relaxed),
|
||||
notified.load(Ordering::Relaxed),
|
||||
);
|
||||
let d0 = smarm::causal::global_delay_cycles();
|
||||
let t = Instant::now();
|
||||
smarm::sleep(Duration::from_millis(700));
|
||||
let secs = t.elapsed().as_secs_f64();
|
||||
println!(
|
||||
"{label}: produced {:.0}/s reserved {:.0}/s notified {:.0}/s injected {:.0}ms(assumed-3GHz)",
|
||||
(produced.load(Ordering::Relaxed) - p0) as f64 / secs,
|
||||
(reserved.load(Ordering::Relaxed) - r0) as f64 / secs,
|
||||
(notified.load(Ordering::Relaxed) - n0) as f64 / secs,
|
||||
(smarm::causal::global_delay_cycles() - d0) as f64 / 3.0e9 * 1e3,
|
||||
);
|
||||
};
|
||||
|
||||
window("no-experiment ");
|
||||
smarm::causal::begin_experiment_for_test("background-compaction", 50);
|
||||
window("bg-comp @ 50% ");
|
||||
smarm::causal::end_experiment_for_test();
|
||||
window("post-experiment");
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
producer.join().unwrap();
|
||||
reserve.join().unwrap();
|
||||
notify.join().unwrap();
|
||||
background.join().unwrap();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user