From 4f58e1c8b92f233af368f7d1698bcead16f9c8e2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 Aug 2025 20:26:22 -1000 Subject: [PATCH] [core] Convert entity vectors to static allocation for reduced memory usage (#10018) --- esphome/core/application.h | 150 ++++++++-------------------- esphome/core/component_iterator.cpp | 13 --- esphome/core/component_iterator.h | 16 ++- esphome/core/config.py | 4 +- esphome/core/defines.h | 23 +++++ esphome/core/helpers.h | 36 +++++++ 6 files changed, 118 insertions(+), 124 deletions(-) diff --git a/esphome/core/application.h b/esphome/core/application.h index a83789837f..b7824a254b 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -216,69 +216,6 @@ class Application { /// Reserve space for components to avoid memory fragmentation void reserve_components(size_t count) { this->components_.reserve(count); } -#ifdef USE_BINARY_SENSOR - void reserve_binary_sensor(size_t count) { this->binary_sensors_.reserve(count); } -#endif -#ifdef USE_SWITCH - void reserve_switch(size_t count) { this->switches_.reserve(count); } -#endif -#ifdef USE_BUTTON - void reserve_button(size_t count) { this->buttons_.reserve(count); } -#endif -#ifdef USE_SENSOR - void reserve_sensor(size_t count) { this->sensors_.reserve(count); } -#endif -#ifdef USE_TEXT_SENSOR - void reserve_text_sensor(size_t count) { this->text_sensors_.reserve(count); } -#endif -#ifdef USE_FAN - void reserve_fan(size_t count) { this->fans_.reserve(count); } -#endif -#ifdef USE_COVER - void reserve_cover(size_t count) { this->covers_.reserve(count); } -#endif -#ifdef USE_CLIMATE - void reserve_climate(size_t count) { this->climates_.reserve(count); } -#endif -#ifdef USE_LIGHT - void reserve_light(size_t count) { this->lights_.reserve(count); } -#endif -#ifdef USE_NUMBER - void reserve_number(size_t count) { this->numbers_.reserve(count); } -#endif -#ifdef USE_DATETIME_DATE - void reserve_date(size_t count) { this->dates_.reserve(count); } -#endif -#ifdef USE_DATETIME_TIME - void reserve_time(size_t count) { this->times_.reserve(count); } -#endif -#ifdef USE_DATETIME_DATETIME - void reserve_datetime(size_t count) { this->datetimes_.reserve(count); } -#endif -#ifdef USE_SELECT - void reserve_select(size_t count) { this->selects_.reserve(count); } -#endif -#ifdef USE_TEXT - void reserve_text(size_t count) { this->texts_.reserve(count); } -#endif -#ifdef USE_LOCK - void reserve_lock(size_t count) { this->locks_.reserve(count); } -#endif -#ifdef USE_VALVE - void reserve_valve(size_t count) { this->valves_.reserve(count); } -#endif -#ifdef USE_MEDIA_PLAYER - void reserve_media_player(size_t count) { this->media_players_.reserve(count); } -#endif -#ifdef USE_ALARM_CONTROL_PANEL - void reserve_alarm_control_panel(size_t count) { this->alarm_control_panels_.reserve(count); } -#endif -#ifdef USE_EVENT - void reserve_event(size_t count) { this->events_.reserve(count); } -#endif -#ifdef USE_UPDATE - void reserve_update(size_t count) { this->updates_.reserve(count); } -#endif #ifdef USE_AREAS void reserve_area(size_t count) { this->areas_.reserve(count); } #endif @@ -394,92 +331,90 @@ class Application { const std::vector &get_areas() { return this->areas_; } #endif #ifdef USE_BINARY_SENSOR - const std::vector &get_binary_sensors() { return this->binary_sensors_; } + auto &get_binary_sensors() const { return this->binary_sensors_; } GET_ENTITY_METHOD(binary_sensor::BinarySensor, binary_sensor, binary_sensors) #endif #ifdef USE_SWITCH - const std::vector &get_switches() { return this->switches_; } + auto &get_switches() const { return this->switches_; } GET_ENTITY_METHOD(switch_::Switch, switch, switches) #endif #ifdef USE_BUTTON - const std::vector &get_buttons() { return this->buttons_; } + auto &get_buttons() const { return this->buttons_; } GET_ENTITY_METHOD(button::Button, button, buttons) #endif #ifdef USE_SENSOR - const std::vector &get_sensors() { return this->sensors_; } + auto &get_sensors() const { return this->sensors_; } GET_ENTITY_METHOD(sensor::Sensor, sensor, sensors) #endif #ifdef USE_TEXT_SENSOR - const std::vector &get_text_sensors() { return this->text_sensors_; } + auto &get_text_sensors() const { return this->text_sensors_; } GET_ENTITY_METHOD(text_sensor::TextSensor, text_sensor, text_sensors) #endif #ifdef USE_FAN - const std::vector &get_fans() { return this->fans_; } + auto &get_fans() const { return this->fans_; } GET_ENTITY_METHOD(fan::Fan, fan, fans) #endif #ifdef USE_COVER - const std::vector &get_covers() { return this->covers_; } + auto &get_covers() const { return this->covers_; } GET_ENTITY_METHOD(cover::Cover, cover, covers) #endif #ifdef USE_LIGHT - const std::vector &get_lights() { return this->lights_; } + auto &get_lights() const { return this->lights_; } GET_ENTITY_METHOD(light::LightState, light, lights) #endif #ifdef USE_CLIMATE - const std::vector &get_climates() { return this->climates_; } + auto &get_climates() const { return this->climates_; } GET_ENTITY_METHOD(climate::Climate, climate, climates) #endif #ifdef USE_NUMBER - const std::vector &get_numbers() { return this->numbers_; } + auto &get_numbers() const { return this->numbers_; } GET_ENTITY_METHOD(number::Number, number, numbers) #endif #ifdef USE_DATETIME_DATE - const std::vector &get_dates() { return this->dates_; } + auto &get_dates() const { return this->dates_; } GET_ENTITY_METHOD(datetime::DateEntity, date, dates) #endif #ifdef USE_DATETIME_TIME - const std::vector &get_times() { return this->times_; } + auto &get_times() const { return this->times_; } GET_ENTITY_METHOD(datetime::TimeEntity, time, times) #endif #ifdef USE_DATETIME_DATETIME - const std::vector &get_datetimes() { return this->datetimes_; } + auto &get_datetimes() const { return this->datetimes_; } GET_ENTITY_METHOD(datetime::DateTimeEntity, datetime, datetimes) #endif #ifdef USE_TEXT - const std::vector &get_texts() { return this->texts_; } + auto &get_texts() const { return this->texts_; } GET_ENTITY_METHOD(text::Text, text, texts) #endif #ifdef USE_SELECT - const std::vector &get_selects() { return this->selects_; } + auto &get_selects() const { return this->selects_; } GET_ENTITY_METHOD(select::Select, select, selects) #endif #ifdef USE_LOCK - const std::vector &get_locks() { return this->locks_; } + auto &get_locks() const { return this->locks_; } GET_ENTITY_METHOD(lock::Lock, lock, locks) #endif #ifdef USE_VALVE - const std::vector &get_valves() { return this->valves_; } + auto &get_valves() const { return this->valves_; } GET_ENTITY_METHOD(valve::Valve, valve, valves) #endif #ifdef USE_MEDIA_PLAYER - const std::vector &get_media_players() { return this->media_players_; } + auto &get_media_players() const { return this->media_players_; } GET_ENTITY_METHOD(media_player::MediaPlayer, media_player, media_players) #endif #ifdef USE_ALARM_CONTROL_PANEL - const std::vector &get_alarm_control_panels() { - return this->alarm_control_panels_; - } + auto &get_alarm_control_panels() const { return this->alarm_control_panels_; } GET_ENTITY_METHOD(alarm_control_panel::AlarmControlPanel, alarm_control_panel, alarm_control_panels) #endif #ifdef USE_EVENT - const std::vector &get_events() { return this->events_; } + auto &get_events() const { return this->events_; } GET_ENTITY_METHOD(event::Event, event, events) #endif #ifdef USE_UPDATE - const std::vector &get_updates() { return this->updates_; } + auto &get_updates() const { return this->updates_; } GET_ENTITY_METHOD(update::UpdateEntity, update, updates) #endif @@ -558,67 +493,68 @@ class Application { std::vector areas_{}; #endif #ifdef USE_BINARY_SENSOR - std::vector binary_sensors_{}; + StaticVector binary_sensors_{}; #endif #ifdef USE_SWITCH - std::vector switches_{}; + StaticVector switches_{}; #endif #ifdef USE_BUTTON - std::vector buttons_{}; + StaticVector buttons_{}; #endif #ifdef USE_EVENT - std::vector events_{}; + StaticVector events_{}; #endif #ifdef USE_SENSOR - std::vector sensors_{}; + StaticVector sensors_{}; #endif #ifdef USE_TEXT_SENSOR - std::vector text_sensors_{}; + StaticVector text_sensors_{}; #endif #ifdef USE_FAN - std::vector fans_{}; + StaticVector fans_{}; #endif #ifdef USE_COVER - std::vector covers_{}; + StaticVector covers_{}; #endif #ifdef USE_CLIMATE - std::vector climates_{}; + StaticVector climates_{}; #endif #ifdef USE_LIGHT - std::vector lights_{}; + StaticVector lights_{}; #endif #ifdef USE_NUMBER - std::vector numbers_{}; + StaticVector numbers_{}; #endif #ifdef USE_DATETIME_DATE - std::vector dates_{}; + StaticVector dates_{}; #endif #ifdef USE_DATETIME_TIME - std::vector times_{}; + StaticVector times_{}; #endif #ifdef USE_DATETIME_DATETIME - std::vector datetimes_{}; + StaticVector datetimes_{}; #endif #ifdef USE_SELECT - std::vector selects_{}; + StaticVector selects_{}; #endif #ifdef USE_TEXT - std::vector texts_{}; + StaticVector texts_{}; #endif #ifdef USE_LOCK - std::vector locks_{}; + StaticVector locks_{}; #endif #ifdef USE_VALVE - std::vector valves_{}; + StaticVector valves_{}; #endif #ifdef USE_MEDIA_PLAYER - std::vector media_players_{}; + StaticVector media_players_{}; #endif #ifdef USE_ALARM_CONTROL_PANEL - std::vector alarm_control_panels_{}; + StaticVector + alarm_control_panels_{}; #endif #ifdef USE_UPDATE - std::vector updates_{}; + StaticVector updates_{}; #endif #ifdef USE_SOCKET_SELECT_SUPPORT diff --git a/esphome/core/component_iterator.cpp b/esphome/core/component_iterator.cpp index 1e8f670d8b..668c4a1fda 100644 --- a/esphome/core/component_iterator.cpp +++ b/esphome/core/component_iterator.cpp @@ -17,19 +17,6 @@ void ComponentIterator::begin(bool include_internal) { this->include_internal_ = include_internal; } -template -void ComponentIterator::process_platform_item_(const std::vector &items, - bool (ComponentIterator::*on_item)(PlatformItem *)) { - if (this->at_ >= items.size()) { - this->advance_platform_(); - } else { - PlatformItem *item = items[this->at_]; - if ((item->is_internal() && !this->include_internal_) || (this->*on_item)(item)) { - this->at_++; - } - } -} - void ComponentIterator::advance_platform_() { this->state_ = static_cast(static_cast(this->state_) + 1); this->at_ = 0; diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index 7a9771b8f2..fdc30485bc 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -172,9 +172,19 @@ class ComponentIterator { uint16_t at_{0}; // Supports up to 65,535 entities per type bool include_internal_{false}; - template - void process_platform_item_(const std::vector &items, - bool (ComponentIterator::*on_item)(PlatformItem *)); + template + void process_platform_item_(const Container &items, + bool (ComponentIterator::*on_item)(typename Container::value_type)) { + if (this->at_ >= items.size()) { + this->advance_platform_(); + } else { + typename Container::value_type item = items[this->at_]; + if ((item->is_internal() && !this->include_internal_) || (this->*on_item)(item)) { + this->at_++; + } + } + } + void advance_platform_(); }; diff --git a/esphome/core/config.py b/esphome/core/config.py index 6d93117164..3bc030ad50 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -421,8 +421,10 @@ async def _add_automations(config): @coroutine_with_priority(-100.0) async def _add_platform_reserves() -> None: + # Generate compile-time entity count defines for static_entity_vector for platform_name, count in sorted(CORE.platform_counts.items()): - cg.add(cg.RawStatement(f"App.reserve_{platform_name}({count});"), prepend=True) + define_name = f"ESPHOME_ENTITY_{platform_name.upper()}_COUNT" + cg.add_define(define_name, count) @coroutine_with_priority(100.0) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 55652e443e..3ed0af91eb 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -239,3 +239,26 @@ // #define USE_BSEC2 // Requires a library with proprietary license #define USE_DASHBOARD_IMPORT + +// Default entity counts for static analysis +#define ESPHOME_ENTITY_ALARM_CONTROL_PANEL_COUNT 1 +#define ESPHOME_ENTITY_BINARY_SENSOR_COUNT 1 +#define ESPHOME_ENTITY_BUTTON_COUNT 1 +#define ESPHOME_ENTITY_CLIMATE_COUNT 1 +#define ESPHOME_ENTITY_COVER_COUNT 1 +#define ESPHOME_ENTITY_DATE_COUNT 1 +#define ESPHOME_ENTITY_DATETIME_COUNT 1 +#define ESPHOME_ENTITY_EVENT_COUNT 1 +#define ESPHOME_ENTITY_FAN_COUNT 1 +#define ESPHOME_ENTITY_LIGHT_COUNT 1 +#define ESPHOME_ENTITY_LOCK_COUNT 1 +#define ESPHOME_ENTITY_MEDIA_PLAYER_COUNT 1 +#define ESPHOME_ENTITY_NUMBER_COUNT 1 +#define ESPHOME_ENTITY_SELECT_COUNT 1 +#define ESPHOME_ENTITY_SENSOR_COUNT 1 +#define ESPHOME_ENTITY_SWITCH_COUNT 1 +#define ESPHOME_ENTITY_TEXT_COUNT 1 +#define ESPHOME_ENTITY_TEXT_SENSOR_COUNT 1 +#define ESPHOME_ENTITY_TIME_COUNT 1 +#define ESPHOME_ENTITY_UPDATE_COUNT 1 +#define ESPHOME_ENTITY_VALVE_COUNT 1 diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 5204804e1e..b05cc11029 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -91,6 +91,42 @@ template<> constexpr int64_t byteswap(int64_t n) { return __builtin_bswap64(n); ///@} +/// @name Container utilities +///@{ + +/// Minimal static vector - saves memory by avoiding std::vector overhead +template class StaticVector { + public: + using value_type = T; + using iterator = typename std::array::iterator; + using const_iterator = typename std::array::const_iterator; + + private: + std::array data_{}; + size_t count_{0}; + + public: + // Minimal vector-compatible interface - only what we actually use + void push_back(const T &value) { + if (count_ < N) { + data_[count_++] = value; + } + } + + size_t size() const { return count_; } + + T &operator[](size_t i) { return data_[i]; } + const T &operator[](size_t i) const { return data_[i]; } + + // For range-based for loops + iterator begin() { return data_.begin(); } + iterator end() { return data_.begin() + count_; } + const_iterator begin() const { return data_.begin(); } + const_iterator end() const { return data_.begin() + count_; } +}; + +///@} + /// @name Mathematics ///@{