perf(timer): inline TimerTable::pop_expired_nonempty
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.
This commit is contained in:
1 file changed
+1
-1
+1
-1
@@ -895,7 +895,7 @@ pub mod __private {
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline]
|
||||
fn pop_expired_nonempty(&mut self, depth: &[u8]) -> Option<(u16, u16)> {
|
||||
let mut best: Option<usize> = None;
|
||||
for (i, e) in self.entries.iter().enumerate() {
|
||||
|
||||
Reference in new issue
Block a user