[core] Inline feed_wdt hot path with out-of-line slow path

Split Application::feed_wdt() into an ALWAYS_INLINE wrapper that checks
the 3ms rate limit against last_wdt_feed_ and a feed_wdt_slow_() callee
that performs the actual arch_feed_wdt() + status LED re-dispatch.

Callers on the hot path (loop_task before/after each component) that
already have a millis() timestamp in hand now pay only a load + sub +
branch on the no-op path instead of a full call8 / entry / retw.

Moves the rate-limit state from a function-local static to a class
member (last_wdt_feed_) so the inline can access it.
This commit is contained in:
J. Nick Koston
2026-04-11 14:32:49 -10:00
parent bef4c8a86c
commit ddbf6f2347
2 changed files with 24 additions and 7 deletions
+6 -5
View File
@@ -196,14 +196,15 @@ void Application::process_dump_config_() {
this->dump_config_at_++;
}
void HOT Application::feed_wdt(uint32_t time) {
static uint32_t last_feed = 0;
void HOT Application::feed_wdt_slow_(uint32_t time) {
// Use provided time if available, otherwise get current time
uint32_t now = time ? time : millis();
// Compare in milliseconds (3ms threshold)
if (now - last_feed > 3) {
// Compare in milliseconds (3ms threshold). The inline wrapper already
// performs this check when time != 0; repeat it here for the time == 0
// entry and as a safety net.
if (now - this->last_wdt_feed_ > 3) {
arch_feed_wdt();
last_feed = now;
this->last_wdt_feed_ = now;
#ifdef USE_STATUS_LED
if (status_led::global_status_led != nullptr) {
status_led::global_status_led->call();
+18 -2
View File
@@ -385,7 +385,19 @@ class Application {
void schedule_dump_config() { this->dump_config_at_ = 0; }
void feed_wdt(uint32_t time = 0);
/// Feed the task watchdog. Hot-path inline rate-limit check: callers that
/// already have a timestamp in hand pay only a load + sub + branch on the
/// common (no-op) path. The actual arch feed + status LED update live in
/// feed_wdt_slow_ to keep this small enough to inline freely.
///
/// Pass time==0 to request millis() be read for you (low-frequency callers
/// only — always takes the slow path).
void ESPHOME_ALWAYS_INLINE feed_wdt(uint32_t time = 0) {
if (time != 0 && static_cast<uint32_t>(time - this->last_wdt_feed_) <= 3) {
return;
}
this->feed_wdt_slow_(time);
}
void reboot();
@@ -615,7 +627,10 @@ class Application {
/// Caller must ensure dump_config_at_ < components_.size().
void __attribute__((noinline)) process_dump_config_();
void feed_wdt_arch_();
/// Slow path for feed_wdt(): actually calls arch_feed_wdt(), updates
/// last_wdt_feed_, and re-dispatches the status LED. Out of line so the
/// inline wrapper stays tiny.
void feed_wdt_slow_(uint32_t time);
/// Perform a delay while also monitoring socket file descriptors for readiness
#ifdef USE_HOST
@@ -669,6 +684,7 @@ class Application {
// 4-byte members
uint32_t last_loop_{0};
uint32_t loop_component_start_time_{0};
uint32_t last_wdt_feed_{0}; // millis() of most recent arch_feed_wdt(); rate-limits feed_wdt() hot path
#ifdef USE_HOST
int max_fd_{-1}; // Highest file descriptor number for select()