From 7d4b11d11240068fcbf1c6f8f00d09ef33e8527f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 21:11:49 -0500 Subject: [PATCH 1/2] Reduce Component memory usage by 40% (8 bytes per component) --- esphome/core/application.cpp | 4 ++ esphome/core/component.cpp | 77 ++++++++++++++++++++++++++++++++---- esphome/core/component.h | 5 ++- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 1599c648e7..d6fab018cc 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -84,6 +84,10 @@ void Application::setup() { } ESP_LOGI(TAG, "setup() finished successfully!"); + + // Clear setup priority overrides to free memory + clear_setup_priority_overrides(); + this->schedule_dump_config(); this->calculate_looping_components_(); } diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 6661223e35..faac36344e 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "esphome/core/application.h" #include "esphome/core/hal.h" @@ -12,6 +13,20 @@ namespace esphome { static const char *const TAG = "component"; +// Global vectors for component data that doesn't belong in every instance. +// Using vector instead of unordered_map for both because: +// - Much lower memory overhead (8 bytes per entry vs 20+ for unordered_map) +// - Linear search is fine for small n (typically < 5 entries) +// - These are rarely accessed (setup only or error cases only) + +// Component error messages - only stores messages for failed components +// Typically 0-2 entries, usually 0 +static std::vector> g_component_error_messages; + +// Setup priority overrides - freed after setup completes +// Typically < 5 entries, lazy allocated +static std::unique_ptr>> g_setup_priority_overrides; + namespace setup_priority { const float BUS = 1000.0f; @@ -102,8 +117,15 @@ void Component::call_setup() { this->setup(); } void Component::call_dump_config() { this->dump_config(); if (this->is_failed()) { - ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), - this->error_message_ ? this->error_message_ : "unspecified"); + // Look up error message from global vector + const char *error_msg = "unspecified"; + for (const auto &pair : g_component_error_messages) { + if (pair.first == this) { + error_msg = pair.second; + break; + } + } + ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), error_msg); } } @@ -245,8 +267,17 @@ void Component::status_set_error(const char *message) { this->component_state_ |= STATUS_LED_ERROR; App.app_state_ |= STATUS_LED_ERROR; ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message); - if (strcmp(message, "unspecified") != 0) - this->error_message_ = message; + if (strcmp(message, "unspecified") != 0) { + // Check if this component already has an error message + for (auto &pair : g_component_error_messages) { + if (pair.first == this) { + pair.second = message; + return; + } + } + // Add new error message + g_component_error_messages.emplace_back(this, message); + } } void Component::status_clear_warning() { if ((this->component_state_ & STATUS_LED_WARNING) == 0) @@ -270,11 +301,36 @@ void Component::status_momentary_error(const std::string &name, uint32_t length) } void Component::dump_config() {} float Component::get_actual_setup_priority() const { - if (std::isnan(this->setup_priority_override_)) - return this->get_setup_priority(); - return this->setup_priority_override_; + // Check if there's an override in the global vector + if (g_setup_priority_overrides) { + // Linear search is fine for small n (typically < 5 overrides) + for (const auto &pair : *g_setup_priority_overrides) { + if (pair.first == this) { + return pair.second; + } + } + } + return this->get_setup_priority(); +} +void Component::set_setup_priority(float priority) { + // Lazy allocate the vector if needed + if (!g_setup_priority_overrides) { + g_setup_priority_overrides = std::make_unique>>(); + // Reserve some space to avoid reallocations (most configs have < 10 overrides) + g_setup_priority_overrides->reserve(10); + } + + // Check if this component already has an override + for (auto &pair : *g_setup_priority_overrides) { + if (pair.first == this) { + pair.second = priority; + return; + } + } + + // Add new override + g_setup_priority_overrides->emplace_back(this, priority); } -void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; } bool Component::has_overridden_loop() const { #if defined(USE_HOST) || defined(CLANG_TIDY) @@ -336,4 +392,9 @@ uint32_t WarnIfComponentBlockingGuard::finish() { WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {} +void clear_setup_priority_overrides() { + // Free the setup priority map completely + g_setup_priority_overrides.reset(); +} + } // namespace esphome diff --git a/esphome/core/component.h b/esphome/core/component.h index 5b37deeb68..ab30466e2d 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -387,9 +387,7 @@ class Component { bool cancel_defer(const std::string &name); // NOLINT // Ordered for optimal packing on 32-bit systems - float setup_priority_override_{NAN}; const char *component_source_{nullptr}; - const char *error_message_{nullptr}; uint16_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_MS}; ///< Warn if blocked for this many ms (max 65.5s) /// State of this component - each bit has a purpose: /// Bits 0-1: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED) @@ -459,4 +457,7 @@ class WarnIfComponentBlockingGuard { Component *component_; }; +// Function to clear setup priority overrides after all components are set up +void clear_setup_priority_overrides(); + } // namespace esphome From adeceee71f41e0f9fff23b1418a33445046bc5ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 21:15:20 -0500 Subject: [PATCH 2/2] Reduce Component memory usage by 40% (8 bytes per component) --- esphome/core/component.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index faac36344e..e45417d5aa 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -20,8 +20,8 @@ static const char *const TAG = "component"; // - These are rarely accessed (setup only or error cases only) // Component error messages - only stores messages for failed components -// Typically 0-2 entries, usually 0 -static std::vector> g_component_error_messages; +// Lazy allocated since most configs have zero failures +static std::unique_ptr>> g_component_error_messages; // Setup priority overrides - freed after setup completes // Typically < 5 entries, lazy allocated @@ -119,10 +119,12 @@ void Component::call_dump_config() { if (this->is_failed()) { // Look up error message from global vector const char *error_msg = "unspecified"; - for (const auto &pair : g_component_error_messages) { - if (pair.first == this) { - error_msg = pair.second; - break; + if (g_component_error_messages) { + for (const auto &pair : *g_component_error_messages) { + if (pair.first == this) { + error_msg = pair.second; + break; + } } } ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), error_msg); @@ -268,15 +270,19 @@ void Component::status_set_error(const char *message) { App.app_state_ |= STATUS_LED_ERROR; ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message); if (strcmp(message, "unspecified") != 0) { + // Lazy allocate the error messages vector if needed + if (!g_component_error_messages) { + g_component_error_messages = std::make_unique>>(); + } // Check if this component already has an error message - for (auto &pair : g_component_error_messages) { + for (auto &pair : *g_component_error_messages) { if (pair.first == this) { pair.second = message; return; } } // Add new error message - g_component_error_messages.emplace_back(this, message); + g_component_error_messages->emplace_back(this, message); } } void Component::status_clear_warning() {