From 6b7258c828d7721050031fa230e96f6c245f8824 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Feb 2026 13:17:22 -0600 Subject: [PATCH 1/2] [core] Conditionally compile setup_priority override infrastructure The setup_priority override mechanism (struct, vector, linear scan, allocation, and cleanup) is only needed when a user explicitly sets setup_priority: in their YAML config. In practice this is almost never used - components define their priorities via C++ virtual methods instead. Gate the entire mechanism behind USE_SETUP_PRIORITY_OVERRIDE, which is only defined when the codegen encounters a setup_priority: config entry. This eliminates dead code (struct, std::vector with reserve, new/delete, linear scan in get_actual_setup_priority) from nearly all builds. Also removes the unnecessary reserve(10) call since the override count is always very small. --- esphome/core/application.cpp | 2 ++ esphome/core/component.cpp | 19 +++++++++++++------ esphome/core/component.h | 4 ++++ esphome/core/defines.h | 1 + esphome/cpp_helpers.py | 3 ++- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 449acc64cf..ed0bd64ce2 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -137,8 +137,10 @@ void Application::setup() { ESP_LOGI(TAG, "setup() finished successfully!"); +#ifdef USE_SETUP_PRIORITY_OVERRIDE // Clear setup priority overrides to free memory clear_setup_priority_overrides(); +#endif #if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) // Set up wake socket for waking main loop from tasks diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 90aa36f4db..4b6a73a36b 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -41,20 +41,23 @@ struct ComponentErrorMessage { bool is_flash_ptr; }; +#ifdef USE_SETUP_PRIORITY_OVERRIDE struct ComponentPriorityOverride { const Component *component; float priority; }; +// Setup priority overrides - freed after setup completes +// Using raw pointer instead of unique_ptr to avoid global constructor/destructor overhead +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +std::vector *setup_priority_overrides = nullptr; +#endif + // Error messages for failed components // Using raw pointer instead of unique_ptr to avoid global constructor/destructor overhead // This is never freed as error messages persist for the lifetime of the device // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) std::vector *component_error_messages = nullptr; -// Setup priority overrides - freed after setup completes -// Using raw pointer instead of unique_ptr to avoid global constructor/destructor overhead -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -std::vector *setup_priority_overrides = nullptr; // Helper to store error messages - reduces duplication between deprecated and new API // Remove before 2026.6.0 when deprecated const char* API is removed @@ -489,6 +492,7 @@ void log_update_interval(const char *tag, PollingComponent *component) { } } float Component::get_actual_setup_priority() const { +#ifdef USE_SETUP_PRIORITY_OVERRIDE // Check if there's an override in the global vector if (setup_priority_overrides) { // Linear search is fine for small n (typically < 5 overrides) @@ -498,14 +502,14 @@ float Component::get_actual_setup_priority() const { } } } +#endif return this->get_setup_priority(); } +#ifdef USE_SETUP_PRIORITY_OVERRIDE void Component::set_setup_priority(float priority) { // Lazy allocate the vector if needed if (!setup_priority_overrides) { setup_priority_overrides = new std::vector(); - // Reserve some space to avoid reallocations (most configs have < 10 overrides) - setup_priority_overrides->reserve(10); } // Check if this component already has an override @@ -519,6 +523,7 @@ void Component::set_setup_priority(float priority) { // Add new override setup_priority_overrides->emplace_back(ComponentPriorityOverride{this, priority}); } +#endif bool Component::has_overridden_loop() const { #if defined(USE_HOST) || defined(CLANG_TIDY) @@ -587,10 +592,12 @@ uint32_t WarnIfComponentBlockingGuard::finish() { WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {} +#ifdef USE_SETUP_PRIORITY_OVERRIDE void clear_setup_priority_overrides() { // Free the setup priority map completely delete setup_priority_overrides; setup_priority_overrides = nullptr; } +#endif } // namespace esphome diff --git a/esphome/core/component.h b/esphome/core/component.h index 9ab77cc2f9..fd99295c86 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -109,7 +109,9 @@ class Component { float get_actual_setup_priority() const; +#ifdef USE_SETUP_PRIORITY_OVERRIDE void set_setup_priority(float priority); +#endif /** priority of loop(). higher -> executed earlier * @@ -562,7 +564,9 @@ class WarnIfComponentBlockingGuard { Component *component_; }; +#ifdef USE_SETUP_PRIORITY_OVERRIDE // Function to clear setup priority overrides after all components are set up void clear_setup_priority_overrides(); +#endif } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 0d6c1a42e8..bcafcb4c60 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -109,6 +109,7 @@ #define USE_SAFE_MODE_CALLBACK #define USE_SELECT #define USE_SENSOR +#define USE_SETUP_PRIORITY_OVERRIDE #define USE_STATUS_LED #define USE_STATUS_SENSOR #define USE_SWITCH diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 2698b9b3d5..954a28d3d1 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -9,7 +9,7 @@ from esphome.const import ( ) from esphome.core import CORE, ID, coroutine from esphome.coroutine import FakeAwaitable -from esphome.cpp_generator import LogStringLiteral, add, get_variable +from esphome.cpp_generator import LogStringLiteral, add, add_define, get_variable from esphome.cpp_types import App from esphome.types import ConfigFragmentType, ConfigType from esphome.util import Registry, RegistryEntry @@ -49,6 +49,7 @@ async def register_component(var, config): ) CORE.component_ids.remove(id_) if CONF_SETUP_PRIORITY in config: + add_define("USE_SETUP_PRIORITY_OVERRIDE") add(var.set_setup_priority(config[CONF_SETUP_PRIORITY])) if CONF_UPDATE_INTERVAL in config: add(var.set_update_interval(config[CONF_UPDATE_INTERVAL])) From e4af9efa4e48e22674ff5b762debbbf2244b8eb7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Feb 2026 13:28:53 -0600 Subject: [PATCH 2/2] [core] Keep declarations unconditional to fix clang-tidy component.h is included before defines.h in application.h, so the #ifdef guards on declarations were evaluated before the define was visible. Keep declarations always visible (harmless unused declarations cost nothing) and only guard the definitions. --- esphome/core/component.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/esphome/core/component.h b/esphome/core/component.h index fd99295c86..bef275f972 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -109,9 +109,7 @@ class Component { float get_actual_setup_priority() const; -#ifdef USE_SETUP_PRIORITY_OVERRIDE void set_setup_priority(float priority); -#endif /** priority of loop(). higher -> executed earlier * @@ -564,9 +562,8 @@ class WarnIfComponentBlockingGuard { Component *component_; }; -#ifdef USE_SETUP_PRIORITY_OVERRIDE // Function to clear setup priority overrides after all components are set up +// Only has an implementation when USE_SETUP_PRIORITY_OVERRIDE is defined void clear_setup_priority_overrides(); -#endif } // namespace esphome