diff --git a/crates/xenia-app/src/main.rs b/crates/xenia-app/src/main.rs index c6344e9..b663d18 100644 --- a/crates/xenia-app/src/main.rs +++ b/crates/xenia-app/src/main.rs @@ -3296,6 +3296,17 @@ fn run_execution( // loop doesn't heap-allocate a `Vec` every iteration. let mut order_buf = [0u8; xenia_cpu::scheduler::HW_THREAD_COUNT]; + // SPIKE (multi-core feasibility, 2026-07-03): env-gated histogram of the + // per-round "runnable width" — how many HW slots hold a Ready thread at + // once. The average width bounds the best-case multi-core speedup (Amdahl): + // lockstep runs the round's slots serially; a perfect host-thread-per-guest + // design runs them concurrently, so wall shrinks by ~avg-width. Zero cost + // unless `XENIA_CONCURRENCY_PROBE` is set. Throwaway diagnostic. + let concurrency_probe = std::env::var("XENIA_CONCURRENCY_PROBE").is_ok(); + let mut width_hist = [0u64; xenia_cpu::scheduler::HW_THREAD_COUNT + 1]; + let mut rounds_with_work = 0u64; + let mut slot_visits = 0u64; + 'outer: loop { // Per-round prologue: budget / shutdown / heartbeat / vsync / // timers / audio-interrupt injection. Carved into @@ -3357,6 +3368,14 @@ fn run_execution( let order_n = kernel.scheduler.round_schedule_into(&mut order_buf); let order = &order_buf[..order_n]; + if concurrency_probe { + width_hist[order_n] += 1; + if order_n > 0 { + rounds_with_work += 1; + slot_visits += order_n as u64; + } + } + if order.is_empty() { // No Ready threads — advance time to the earliest pending // deadline, fire timers, handle deadline wakes, and on hard @@ -3432,6 +3451,31 @@ fn run_execution( RoundCtl::Continue => {} } } + if concurrency_probe { + let avg_width = if rounds_with_work > 0 { + slot_visits as f64 / rounds_with_work as f64 + } else { + 0.0 + }; + eprintln!("=== XENIA_CONCURRENCY_PROBE (multi-core Amdahl ceiling) ==="); + for (w, &c) in width_hist.iter().enumerate() { + let pct = if rounds_with_work + width_hist[0] > 0 { + 100.0 * c as f64 / (rounds_with_work + width_hist[0]) as f64 + } else { + 0.0 + }; + eprintln!(" runnable-width {w}: {c:>12} rounds ({pct:>5.1}%)"); + } + eprintln!( + " rounds_with_work={rounds_with_work} slot_visits={slot_visits} \ + idle_rounds={}", + width_hist[0] + ); + eprintln!( + " AVG RUNNABLE WIDTH = {avg_width:.3} => best-case multi-core \ + speedup ceiling ~= {avg_width:.2}x (concurrent slots / round)" + ); + } stats }