From 9851873e42c4e4d927d0401cc6897c283191f0f6 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Fri, 3 Jul 2026 23:03:46 +0200 Subject: [PATCH] [iterate-4B] perf: quick hot-path wins (bulk quantum-decrement + ctx coalesce) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two byte-identical interpreter-dispatch optimizations (headless golden n200m unchanged). Together ~13% faster on the -n 200M --gpu-inline benchmark (4.33s -> ~3.8s, ~46 -> ~53 MIPS). - scheduler.rs: `decrement_quantum_by(n)` — the superblock epilogue looped `for _ in 0..executed { decrement_quantum() }` (~one bounds-checked call per retired guest instruction, the largest fixed per-superblock cost). QUANTUM_DEFAULT (50k) >> a superblock's instr count, so the quantum boundary is crossed at most once/call: common path is one subtraction, the rare boundary step defers to decrement_quantum for exact rotation (reload + same-priority peer hand-off) semantics. Byte-identical. - main.rs run_superblock: resolve the running thread's PpcContext ONCE per block (was three `ctx_mut_ref` double-indexed slot lookups: cycle-before, the step, cycle-after). Profiled remaining breakdown (-n 300M --gpu-inline): step_block body 50% (67 MIPS, the JIT target), block decode/cache 9% (page-version-bound), kernel HLE 8%, scheduler/lock remainder ~31%. Sub-10% cheap wins remain (block-linking is page-version-gated so only partial); the 5-10x lift is the JIT. Co-Authored-By: Claude Opus 4.8 --- crates/xenia-app/src/main.rs | 22 +++++++-------- crates/xenia-cpu/src/scheduler.rs | 46 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/crates/xenia-app/src/main.rs b/crates/xenia-app/src/main.rs index 26b2071..c6344e9 100644 --- a/crates/xenia-app/src/main.rs +++ b/crates/xenia-app/src/main.rs @@ -2946,9 +2946,9 @@ fn worker_epilogue( stats.instruction_count = stats.instruction_count.wrapping_add(executed); - for _ in 0..executed { - kernel.scheduler.decrement_quantum(); - } + // PERF: byte-identical bulk decrement (was `for _ in 0..executed`), the + // largest fixed per-superblock cost — see `Scheduler::decrement_quantum_by`. + kernel.scheduler.decrement_quantum_by(executed); match result { StepResult::Continue => {} @@ -3142,19 +3142,19 @@ fn run_superblock( let mut total_executed: u64 = 0; let (result, last_block_ptr, last_pc_before) = loop { - let cycle_before = kernel.scheduler.ctx_mut_ref(thread_ref).cycle_count; let mmio_before = mem.mmio_access_count(); let block = unsafe { &*block_ptr }; let _prof_t0 = xenia_gpu::prof::is_on().then(std::time::Instant::now); - let result = { + // PERF: resolve the running thread's context ONCE per block (was three + // `ctx_mut_ref` slot lookups — for cycle-before, the step, and + // cycle-after — each a double bounds-checked index). Byte-identical. + let (result, executed) = { let ctx = kernel.scheduler.ctx_mut_ref(thread_ref); - step_block(ctx, mem, block) + let cycle_before = ctx.cycle_count; + let result = step_block(ctx, mem, block); + let executed = ctx.cycle_count.saturating_sub(cycle_before); + (result, executed) }; - let executed = kernel - .scheduler - .ctx_mut_ref(thread_ref) - .cycle_count - .saturating_sub(cycle_before); if let Some(t0) = _prof_t0 { use xenia_gpu::prof; prof::add(&prof::STEP_NS, t0.elapsed().as_nanos() as u64); diff --git a/crates/xenia-cpu/src/scheduler.rs b/crates/xenia-cpu/src/scheduler.rs index b8d3611..9b7fc65 100644 --- a/crates/xenia-cpu/src/scheduler.rs +++ b/crates/xenia-cpu/src/scheduler.rs @@ -1016,6 +1016,52 @@ impl Scheduler { false } + /// Bulk equivalent of calling [`Self::decrement_quantum`] exactly `n` + /// times, producing a **byte-identical** final scheduler state. PERF: the + /// superblock epilogue used to loop `for _ in 0..executed { decrement_quantum() }` + /// — up to ~128 bounds-checked calls per superblock, ~one per retired guest + /// instruction across the whole run (the single largest fixed per-superblock + /// cost). Since `QUANTUM_DEFAULT` (50_000) ≫ a superblock's instruction + /// count, the quantum boundary is crossed at most once per call, so the + /// common path is a single subtraction (O(1)); only the rare + /// boundary-crossing step defers to `decrement_quantum` to reproduce the + /// exact rotation semantics (reload + same-priority peer hand-off). + pub fn decrement_quantum_by(&mut self, mut n: u64) { + while n > 0 { + let Some(r) = self.current else { + return; + }; + let Some(t) = self.slots[r.hw_id as usize] + .runqueue + .get_mut(r.idx as usize) + else { + return; + }; + let q = t.quantum_remaining as u64; + if q > n { + // No quantum boundary within these `n` steps — the common + // case. Identical to `n` plain decrements that each hit the + // early `quantum_remaining != 0` return. + t.quantum_remaining = (q - n) as u32; + return; + } + // A rotation (quantum reload + optional peer hand-off) occurs + // within these `n` steps. Collapse the `q` leading no-op + // decrements into one rotating single-step: set the quantum to 1 + // so the next `decrement_quantum` drives it to 0 and rotates with + // identical semantics (for `q == 0` the first single-step already + // rotates, consuming exactly one step). + let consumed = if q > 0 { + t.quantum_remaining = 1; + q + } else { + 1 + }; + self.decrement_quantum(); + n -= consumed; + } + } + /// Cooperative yield: the currently-running thread executed a `db16cyc` /// spin-wait hint (see `StepResult::Yield`). It is busy-spinning on a /// guest spinlock/barrier whose release depends on a *co-located* peer