mirror of
https://github.com/esphome/esphome.git
synced 2026-09-01 18:46:02 +00:00
[core] Trim Scheduler::call fast path
Two small wins on the per-loop Scheduler::call path:
1. Mark the scheduler's `millis_64()` wrapper `ESPHOME_ALWAYS_INLINE`.
The inner `esphome::millis_64()` is already always_inline, but the
wrapper was not, so GCC emitted an out-of-line `$isra$0` clone with
its own `entry`/`retw` register-window prologue/epilogue at every
call site. Inlining removes the wrapper's call overhead and the
register-window thunk on ESP32.
2. Collapse the two atomic reads of `to_remove_` in `Scheduler::call`
into one. The previous sequence
cleanup_(); // loads to_remove_
if (to_remove_count_() >= MAX_...) ... // reloads to_remove_
produced `memw; l32i; beqz; memw; l32i; bltui` on the fast path
because the compiler cannot CSE across the `memw` barriers that
std::atomic<uint32_t>::load emits on Xtensa. Reading the counter
once and branching on the result leaves a single
`memw; l32i; beqz` on the common zero-case; the slow path
(cleanup_slow_path_ + re-read + optional full_cleanup) pays an
extra read but already holds the scheduler mutex.
This commit is contained in:
@@ -601,14 +601,21 @@ uint32_t HOT Scheduler::call(uint32_t now) {
|
||||
}
|
||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||
|
||||
// Cleanup removed items before processing
|
||||
// First try to clean items from the top of the heap (fast path)
|
||||
this->cleanup_();
|
||||
// Cleanup removed items before processing. Read the counter once so the
|
||||
// common zero-case is a single atomic load + branch; the old code called
|
||||
// cleanup_() (which loads to_remove_) and then to_remove_count_() again for
|
||||
// the MAX check, producing a redundant memw+load pair on the fast path.
|
||||
if (this->to_remove_count_() != 0) {
|
||||
// First try to clean items from the top of the heap (fast path).
|
||||
this->cleanup_slow_path_();
|
||||
|
||||
// If we still have too many cancelled items, do a full cleanup
|
||||
// This only happens if cancelled items are stuck in the middle/bottom of the heap
|
||||
if (this->to_remove_count_() >= MAX_LOGICALLY_DELETED_ITEMS) {
|
||||
this->full_cleanup_removed_items_();
|
||||
// If we still have too many cancelled items, do a full cleanup.
|
||||
// This only happens if cancelled items are stuck in the middle/bottom
|
||||
// of the heap. Re-read to_remove_ because cleanup_slow_path_ may have
|
||||
// decremented it.
|
||||
if (this->to_remove_count_() >= MAX_LOGICALLY_DELETED_ITEMS) {
|
||||
this->full_cleanup_removed_items_();
|
||||
}
|
||||
}
|
||||
// IMPORTANT: This loop uses index-based access (items_[0]), NOT iterators.
|
||||
// This is intentional — fired intervals are pushed back into items_ via
|
||||
|
||||
@@ -118,7 +118,7 @@ class Scheduler {
|
||||
bool cancel_retry(Component *component, uint32_t id);
|
||||
|
||||
/// Get 64-bit millisecond timestamp (handles 32-bit millis() rollover)
|
||||
uint64_t millis_64() { return esphome::millis_64(); }
|
||||
uint64_t ESPHOME_ALWAYS_INLINE millis_64() { return esphome::millis_64(); }
|
||||
|
||||
// Calculate when the next scheduled item should run.
|
||||
// @param now On ESP32, unused for 64-bit extension (native); on other platforms, extended to 64-bit via rollover.
|
||||
|
||||
Reference in New Issue
Block a user