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:
smarm
2026-05-25 23:28:11 +02:00
parent 64be3f23ac
commit 72f5d38e5d
4 changed files with 216 additions and 144 deletions
+83 -83
View File
@@ -2,185 +2,185 @@
"chained_spawn": {
"smarm 1-thread": {
"result": 1000,
"median": 9763,
"min": 8852,
"max": 10520
"median": 273,
"min": 261,
"max": 292
},
"smarm 24-thread": {
"result": 1000,
"median": 23562,
"min": 20515,
"max": 24398
"median": 837,
"min": 788,
"max": 913
},
"tokio current_thread": {
"result": 1000,
"median": 70,
"median": 68,
"min": 61,
"max": 75
"max": 113
},
"tokio multi-thread": {
"result": 1000,
"median": 187,
"min": 158,
"max": 228
"median": 200,
"min": 176,
"max": 230
}
},
"yield_many": {
"smarm 1-thread": {
"result": 200000,
"median": 21704,
"min": 20353,
"max": 22053
"median": 19465,
"min": 19299,
"max": 20007
},
"smarm 24-thread": {
"result": 200000,
"median": 180752,
"min": 148576,
"max": 187771
"median": 163921,
"min": 155700,
"max": 167921
},
"tokio current_thread": {
"result": 200000,
"median": 5816,
"min": 5567,
"max": 6278
"median": 5022,
"min": 5001,
"max": 5530
},
"tokio multi-thread": {
"result": 200000,
"median": 8259,
"min": 7760,
"max": 9610
"median": 8341,
"min": 7412,
"max": 9199
}
},
"fan_out_compute": {
"smarm 1-thread": {
"result": 33860,
"median": 14009,
"min": 13834,
"max": 14274
"median": 14096,
"min": 13982,
"max": 17406
},
"smarm 24-thread": {
"result": 33860,
"median": 2839,
"min": 2702,
"max": 3019
"median": 2475,
"min": 2264,
"max": 2589
},
"tokio current_thread": {
"result": 33860,
"median": 13083,
"min": 12634,
"max": 15737
"median": 12640,
"min": 12548,
"max": 15881
},
"tokio multi-thread": {
"result": 33860,
"median": 1538,
"min": 1434,
"max": 2201
"median": 1483,
"min": 1395,
"max": 1710
}
},
"ping_pong_oneshot": {
"smarm 1-thread": {
"result": 1000,
"median": 18409,
"min": 16381,
"max": 21858
"median": 801,
"min": 720,
"max": 929
},
"smarm 24-thread": {
"result": 1000,
"median": 44596,
"min": 41804,
"max": 48324
"median": 1449,
"min": 1292,
"max": 1530
},
"tokio current_thread": {
"result": 1000,
"median": 494,
"min": 465,
"max": 602
"median": 398,
"min": 369,
"max": 407
},
"tokio multi-thread": {
"result": 1000,
"median": 10907,
"min": 9582,
"max": 12430
"median": 10497,
"min": 9238,
"max": 11961
}
},
"deep_recursion": {
"smarm 1-thread": {
"result": 1,
"median": 123,
"min": 108,
"max": 169
"median": 136,
"min": 117,
"max": 163
},
"smarm 24-thread": {
"result": 1,
"median": 633,
"min": 558,
"max": 783
"median": 743,
"min": 654,
"max": 845
},
"tokio current_thread": {
"result": 1,
"median": 12,
"min": 11,
"max": 14
"median": 14,
"min": 14,
"max": 16
},
"tokio multi-thread": {
"result": 1,
"median": 52,
"min": 42,
"max": 55
"median": 61,
"min": 47,
"max": 94
}
},
"yield_in_hot_loop": {
"smarm 1-thread": {
"result": 1000000,
"median": 82273,
"min": 81870,
"max": 86041
"median": 83281,
"min": 80264,
"max": 85697
},
"tokio current_thread": {
"result": 1000000,
"median": 75353,
"min": 71841,
"max": 77573
"median": 75538,
"min": 69842,
"max": 78823
}
},
"uncontended_channel": {
"smarm 1-thread": {
"result": 1000000,
"median": 9390,
"min": 9331,
"max": 12504
"median": 9440,
"min": 9393,
"max": 13168
},
"tokio current_thread": {
"result": 1000000,
"median": 17007,
"min": 16933,
"max": 17657
"median": 17817,
"min": 16926,
"max": 18572
}
},
"catch_unwind_panics": {
"smarm 1-thread": {
"result": 10000,
"median": 144807,
"min": 138473,
"max": 153664
"median": 133840,
"min": 130425,
"max": 139709
},
"smarm 24-thread": {
"result": 10000,
"median": 267812,
"min": 229587,
"max": 288329
"median": 128729,
"min": 108091,
"max": 132700
},
"tokio current_thread": {
"result": 10000,
"median": 11084,
"min": 10904,
"max": 12041
"median": 10681,
"min": 10158,
"max": 11644
},
"tokio multi-thread": {
"result": 10000,
"median": 6029,
"min": 5054,
"max": 7216
"median": 6700,
"min": 2778,
"max": 7453
}
}
}