From 8cd1aea6468e477d5132e3ea74784ef118ed3569 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Mon, 4 May 2026 13:17:59 -0600 Subject: [PATCH] perf(timer): inline TimerTable::pop_expired_nonempty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the #[cold] attribute and add #[inline]. The hint-cold was keeping the body out-of-line at every call site (one per chart that declares any after() trigger), which forced a real function call through the x86_64 ABI plus its register-spill prologue/epilogue — ~10-15 instructions of pure framing on top of the body's own cost. Inlining lets LLVM fold the body into the codegen-emitted timer_pop_block at step()'s timer-pop site; for the timer_fire bench, run_handlers also gets inlined into step() as a knock-on, because the inliner's budget is no longer being spent on a separate pop_expired_nonempty call. Bench delta (vs. after_opt2_unchecked, instructions): timer_fire_dispatch 212 → 175 -17% bubble_top 94 → 93 -1% (incidental) others: unchanged Code-size cost: pop_expired_nonempty is short and inlines cheaply; the binary size before/after differs by a handful of bytes per chart-with-timers call site. The original #[cold] attribute appears to have been a guess (timer arming is rare → mark cold) that was wrong: when a chart has timers at all, the empty-check in pop_expired is the cold path; pop_expired_nonempty itself runs on every step() of any state with an active timer. --- hsmc/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hsmc/src/lib.rs b/hsmc/src/lib.rs index 1ea24d8..e1a5db8 100644 --- a/hsmc/src/lib.rs +++ b/hsmc/src/lib.rs @@ -895,7 +895,7 @@ pub mod __private { } } - #[cold] + #[inline] fn pop_expired_nonempty(&mut self, depth: &[u8]) -> Option<(u16, u16)> { let mut best: Option = None; for (i, e) in self.entries.iter().enumerate() {