From 39df2ab47fb66bc6d627f18ff3b467aa8b3f82a8 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 | 22 ++++++++++++++++++---- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index cce0c7b3e0..60828eb1fe 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_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 7266f57e15..20a6d2311d 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -6,6 +6,7 @@ #include #include "esphome/core/defines.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/optional.h" @@ -574,9 +575,10 @@ class PollingComponent : public Component { uint32_t update_interval_; }; -#ifdef USE_RUNTIME_STATS -uint32_t micros(); // Forward declare for inline constructor -#endif +// millis() and micros() are available via hal.h + +// Cold path for blocking warning - defined in component.cpp +void warn_blocking(Component *component, uint32_t blocking_time); class WarnIfComponentBlockingGuard { public: @@ -591,7 +593,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_blocking(this->component_, blocking_time); + } + return curr_time; + } ~WarnIfComponentBlockingGuard() = default; @@ -600,6 +613,7 @@ class WarnIfComponentBlockingGuard { Component *component_; #ifdef USE_RUNTIME_STATS uint32_t started_us_; + void record_runtime_stats_(); #endif };