From ea1cfa9c1efb1a2ab725556e46c67797e27984f1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 13 Mar 2026 20:50:15 -1000 Subject: [PATCH] [core] Inline WarnIfComponentBlockingGuard::finish() into header The fast path (millis + subtract + compare) is tiny and called once per component per loop iteration. Moving it inline eliminates a call8/retw pair per component, reducing main loop overhead. The cold warning path (warn_if_blocking) and runtime stats recording remain out-of-line in component.cpp. Co-Authored-By: Claude Opus 4.6 (1M context) --- esphome/core/component.cpp | 12 +++--------- esphome/core/component.h | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index cce0c7b3e04..4387a5cf907 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -509,7 +509,7 @@ void PollingComponent::stop_poller() { uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; } void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } -static void __attribute__((noinline, cold)) warn_blocking(Component *component, uint32_t blocking_time) { +void __attribute__((noinline, cold)) warn_if_blocking(Component *component, uint32_t blocking_time) { bool should_warn; if (component != nullptr) { should_warn = component->should_warn_of_blocking(blocking_time); @@ -523,10 +523,8 @@ static void __attribute__((noinline, cold)) warn_blocking(Component *component, } } -uint32_t WarnIfComponentBlockingGuard::finish() { - uint32_t curr_time = millis(); - uint32_t blocking_time = curr_time - this->started_; #ifdef USE_RUNTIME_STATS +void WarnIfComponentBlockingGuard::record_runtime_stats_() { // Use micros() for accurate sub-millisecond timing. millis() has insufficient // resolution — most components complete in microseconds but millis() only has // 1ms granularity, so results were essentially random noise. @@ -534,12 +532,8 @@ uint32_t WarnIfComponentBlockingGuard::finish() { uint32_t duration_us = micros() - this->started_us_; global_runtime_stats->record_component_time(this->component_, duration_us); } -#endif - if (blocking_time > WARN_IF_BLOCKING_OVER_MS) { - warn_blocking(this->component_, blocking_time); - } - return curr_time; } +#endif #ifdef USE_SETUP_PRIORITY_OVERRIDE void clear_setup_priority_overrides() { diff --git a/esphome/core/component.h b/esphome/core/component.h index 7266f57e151..a1eaca022dd 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -574,10 +574,14 @@ class PollingComponent : public Component { uint32_t update_interval_; }; +uint32_t millis(); // Forward declare for inline finish() #ifdef USE_RUNTIME_STATS uint32_t micros(); // Forward declare for inline constructor #endif +// Cold path for blocking warning - defined in component.cpp +void warn_if_blocking(Component *component, uint32_t blocking_time); + class WarnIfComponentBlockingGuard { public: WarnIfComponentBlockingGuard(Component *component, uint32_t start_time) @@ -591,7 +595,18 @@ class WarnIfComponentBlockingGuard { } // Finish the timing operation and return the current time - uint32_t finish(); + // Inlined: the fast path is just millis() + subtract + compare + inline uint32_t HOT finish() { + uint32_t curr_time = millis(); + uint32_t blocking_time = curr_time - this->started_; +#ifdef USE_RUNTIME_STATS + this->record_runtime_stats_(); +#endif + if (blocking_time > WARN_IF_BLOCKING_OVER_MS) [[unlikely]] { + warn_if_blocking(this->component_, blocking_time); + } + return curr_time; + } ~WarnIfComponentBlockingGuard() = default; @@ -600,6 +615,7 @@ class WarnIfComponentBlockingGuard { Component *component_; #ifdef USE_RUNTIME_STATS uint32_t started_us_; + void record_runtime_stats_(); #endif };