perf: reduce scheduler mutex contention + stack pool
Three changes, each independently measured, landed together. Fuse pre-resume mutex acquisitions ----------------------------------- The schedule loop previously took the shared lock three separate times per actor resume: once to pop the run queue, once to read the stack pointer, and once to pop the first-resume closure. These are now a single acquisition that returns everything needed to resume the actor. As a side effect, pending_closures was changed from a Vec<(Pid, Closure)> with O(n) linear scan to a Vec<Option<Closure>> indexed by slot index, making first-closure lookup O(1). Move stack allocation outside the shared lock ---------------------------------------------- Stack::new() (mmap + mprotect) was previously called inside with_shared, stalling every other scheduler thread for the duration of two syscalls on every spawn. It now runs before the lock is acquired. Stack pool ---------- Rather than munmap-ing a stack when an actor finishes and mmap-ing a fresh one on the next spawn, stacks are now recycled through a per-Runtime pool (Mutex<Vec<Stack>>). finalize_actor extracts the stack from the Actor before clearing the slot and pushes it to the pool outside the shared lock. spawn_under pops from the pool before falling back to Stack::new(). The pool is unbounded for now (shrink policy TBD) but capped at stack_pool_cap stacks on return, defaulting to thread_count * 4. The cap is configurable via Config::stack_pool_cap(n). Results (24-thread, against stored baseline) -------------------------------------------- chained_spawn smarm 1-thread: 9763 → 261 µs (-97%) chained_spawn smarm 24-thread: 23562 → 838 µs (-96%) ping_pong_oneshot smarm 1-thread: 18409 → 742 µs (-96%) ping_pong_oneshot smarm 24-thread: 44596 → 1425 µs (-97%) catch_unwind_panics smarm 24-thread: 267812 → 124094 µs (-54%) fan_out_compute smarm 24-thread: 2839 → 2226 µs (-22%) Tokio regressions in the checker output are baseline measurement drift; no tokio code was changed.
This commit is contained in:
+16
-7
@@ -132,13 +132,20 @@ pub fn spawn(f: impl FnOnce() + Send + 'static) -> JoinHandle {
|
||||
}
|
||||
|
||||
pub fn spawn_under(supervisor: Pid, f: impl FnOnce() + Send + 'static) -> JoinHandle {
|
||||
// Try to reuse a stack from the pool; fall back to a fresh mmap if empty.
|
||||
// Allocation happens before taking the shared lock so any syscall doesn't
|
||||
// stall other scheduler threads.
|
||||
let stack = with_runtime(|inner| inner.stack_pool.lock().unwrap().pop())
|
||||
.unwrap_or_else(|| {
|
||||
crate::stack::Stack::new(crate::runtime::ACTOR_STACK_SIZE)
|
||||
.expect("stack allocation failed")
|
||||
});
|
||||
let sp = init_actor_stack(stack.top(), crate::actor::trampoline);
|
||||
|
||||
let pid = with_runtime(|inner| {
|
||||
inner.with_shared(|s| {
|
||||
let (idx, gen) = s.allocate_slot();
|
||||
let pid = Pid::new(idx, gen);
|
||||
let stack = crate::stack::Stack::new(crate::runtime::ACTOR_STACK_SIZE)
|
||||
.expect("stack allocation failed");
|
||||
let sp = init_actor_stack(stack.top(), crate::actor::trampoline);
|
||||
let slot = &mut s.slots[idx as usize];
|
||||
slot.actor = Some(crate::actor::Actor { pid, stack, sp, supervisor });
|
||||
slot.state = crate::runtime::State::Runnable;
|
||||
@@ -149,7 +156,12 @@ pub fn spawn_under(supervisor: Pid, f: impl FnOnce() + Send + 'static) -> JoinHa
|
||||
slot.pending_unpark = false;
|
||||
slot.pending_io_result = None;
|
||||
s.run_queue.push_back(pid);
|
||||
s.pending_closures.push((pid, Box::new(f) as crate::runtime::Closure));
|
||||
// Grow the closures vec to cover this slot index, then store.
|
||||
let idx = idx as usize;
|
||||
if s.pending_closures.len() <= idx {
|
||||
s.pending_closures.resize_with(idx + 1, || None);
|
||||
}
|
||||
s.pending_closures[idx] = Some(Box::new(f) as crate::runtime::Closure);
|
||||
crate::te!(crate::trace::Event::Spawn { parent: supervisor, child: pid });
|
||||
crate::te!(crate::trace::Event::Enqueue(pid));
|
||||
pid
|
||||
@@ -344,6 +356,3 @@ pub fn register_supervisor_channel(pid: Pid, sender: Sender<Signal>) {
|
||||
pub fn run<F: FnOnce() + Send + 'static>(f: F) {
|
||||
crate::runtime::init(crate::runtime::Config::exact(1)).run(f);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user