1
0
mirror of https://github.com/esphome/esphome.git synced 2025-06-15 14:56:59 +02:00

Reserve memory for component and platform vectors (#9042)

This commit is contained in:
J. Nick Koston 2025-06-11 05:05:42 -05:00 committed by GitHub
parent a488c8cd5c
commit 3411e45a0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 125 additions and 10 deletions

View File

@ -235,6 +235,7 @@ async def register_alarm_control_panel(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_alarm_control_panel(var))
CORE.register_platform_component("alarm_control_panel", var)
await setup_alarm_control_panel_core_(var, config)

View File

@ -554,6 +554,7 @@ async def register_binary_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_binary_sensor(var))
CORE.register_platform_component("binary_sensor", var)
await setup_binary_sensor_core_(var, config)

View File

@ -108,6 +108,7 @@ async def register_button(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_button(var))
CORE.register_platform_component("button", var)
await setup_button_core_(var, config)

View File

@ -443,6 +443,7 @@ async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_climate(var))
CORE.register_platform_component("climate", var)
await setup_climate_core_(var, config)

View File

@ -189,6 +189,7 @@ async def register_cover(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_cover(var))
CORE.register_platform_component("cover", var)
await setup_cover_core_(var, config)

View File

@ -158,7 +158,9 @@ async def setup_datetime_core_(var, config):
async def register_datetime(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(getattr(cg.App, f"register_{config[CONF_TYPE].lower()}")(var))
entity_type = config[CONF_TYPE].lower()
cg.add(getattr(cg.App, f"register_{entity_type}")(var))
CORE.register_platform_component(entity_type, var)
await setup_datetime_core_(var, config)
cg.add_define(f"USE_DATETIME_{config[CONF_TYPE]}")

View File

@ -113,6 +113,7 @@ async def register_event(var, config, *, event_types: list[str]):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_event(var))
CORE.register_platform_component("event", var)
await setup_event_core_(var, config, event_types=event_types)

View File

@ -296,6 +296,7 @@ async def register_fan(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_fan(var))
CORE.register_platform_component("fan", var)
await setup_fan_core_(var, config)

View File

@ -37,7 +37,7 @@ from esphome.const import (
CONF_WEB_SERVER,
CONF_WHITE,
)
from esphome.core import coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
@ -270,6 +270,7 @@ async def setup_light_core_(light_var, output_var, config):
async def register_light(output_var, config):
light_var = cg.new_Pvariable(config[CONF_ID], output_var)
cg.add(cg.App.register_light(light_var))
CORE.register_platform_component("light", light_var)
await cg.register_component(light_var, config)
await setup_light_core_(light_var, output_var, config)

View File

@ -115,6 +115,7 @@ async def register_lock(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_lock(var))
CORE.register_platform_component("lock", var)
await _setup_lock_core(var, config)

View File

@ -103,6 +103,7 @@ async def register_media_player(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_media_player(var))
CORE.register_platform_component("media_player", var)
await setup_media_player_core_(var, config)

View File

@ -277,6 +277,7 @@ async def register_number(
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_number(var))
CORE.register_platform_component("number", var)
await setup_number_core_(
var, config, min_value=min_value, max_value=max_value, step=step
)

View File

@ -111,6 +111,7 @@ async def register_select(var, config, *, options: list[str]):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_select(var))
CORE.register_platform_component("select", var)
await setup_select_core_(var, config, options=options)

View File

@ -167,7 +167,6 @@ DEVICE_CLASSES = [
]
_LOGGER = logging.getLogger(__name__)
sensor_ns = cg.esphome_ns.namespace("sensor")
StateClasses = sensor_ns.enum("StateClass")
STATE_CLASSES = {
@ -840,6 +839,7 @@ async def register_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_sensor(var))
CORE.register_platform_component("sensor", var)
await setup_sensor_core_(var, config)

View File

@ -159,6 +159,7 @@ async def register_switch(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_switch(var))
CORE.register_platform_component("switch", var)
await setup_switch_core_(var, config)

View File

@ -126,6 +126,7 @@ async def register_text(
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_text(var))
CORE.register_platform_component("text", var)
await setup_text_core_(
var, config, min_length=min_length, max_length=max_length, pattern=pattern
)

View File

@ -215,6 +215,7 @@ async def register_text_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_text_sensor(var))
CORE.register_platform_component("text_sensor", var)
await setup_text_sensor_core_(var, config)

View File

@ -111,6 +111,7 @@ async def register_update(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_update(var))
CORE.register_platform_component("update", var)
await setup_update_core_(var, config)

View File

@ -163,6 +163,7 @@ async def register_valve(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_valve(var))
CORE.register_platform_component("valve", var)
await _setup_valve_core(var, config)

View File

@ -1,3 +1,4 @@
from collections import defaultdict
import logging
import math
import os
@ -516,6 +517,9 @@ class EsphomeCore:
self.loaded_platforms: set[str] = set()
# A set of component IDs to track what Component subclasses are declared
self.component_ids = set()
# Dict to track platform entity counts for pre-allocation
# Key: platform name (e.g. "sensor", "binary_sensor"), Value: count
self.platform_counts: defaultdict[str, int] = defaultdict(int)
# Whether ESPHome was started in verbose mode
self.verbose = False
# Whether ESPHome was started in quiet mode
@ -545,6 +549,7 @@ class EsphomeCore:
self.platformio_options = {}
self.loaded_integrations = set()
self.component_ids = set()
self.platform_counts = defaultdict(int)
PIN_SCHEMA_REGISTRY.reset()
@property
@ -669,16 +674,17 @@ class EsphomeCore:
def using_esp_idf(self):
return self.target_framework == "esp-idf"
def add_job(self, func, *args, **kwargs):
def add_job(self, func, *args, **kwargs) -> None:
self.event_loop.add_job(func, *args, **kwargs)
def flush_tasks(self):
def flush_tasks(self) -> None:
try:
self.event_loop.flush_tasks()
except RuntimeError as e:
raise EsphomeError(str(e)) from e
def add(self, expression):
def add(self, expression, prepend=False) -> "Statement":
"""Add an expression or statement to the main setup() block."""
from esphome.cpp_generator import Expression, Statement, statement
if isinstance(expression, Expression):
@ -688,11 +694,14 @@ class EsphomeCore:
f"Add '{expression}' must be expression or statement, not {type(expression)}"
)
if prepend:
self.main_statements.insert(0, expression)
else:
self.main_statements.append(expression)
_LOGGER.debug("Adding: %s", expression)
return expression
def add_global(self, expression, prepend=False):
def add_global(self, expression, prepend=False) -> "Statement":
from esphome.cpp_generator import Expression, Statement, statement
if isinstance(expression, Expression):
@ -822,6 +831,14 @@ class EsphomeCore:
def has_id(self, id):
return id in self.variables
def register_platform_component(self, platform_name: str, var) -> None:
"""Register a component for a platform and track its count.
:param platform_name: The name of the platform (e.g., 'sensor', 'binary_sensor')
:param var: The variable (component) being registered (currently unused but kept for future use)
"""
self.platform_counts[platform_name] += 1
@property
def cpp_main_section(self):
from esphome.cpp_generator import statement

View File

@ -198,6 +198,73 @@ class Application {
void register_update(update::UpdateEntity *update) { this->updates_.push_back(update); }
#endif
/// 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
/// Register the component in this Application instance.
template<class C> C *register_component(C *c) {
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");

View File

@ -329,6 +329,12 @@ async def _add_automations(config):
await automation.build_automation(trigger, [], conf)
@coroutine_with_priority(-100.0)
async def _add_platform_reserves() -> None:
for platform_name, count in sorted(CORE.platform_counts.items()):
cg.add(cg.RawStatement(f"App.reserve_{platform_name}({count});"), prepend=True)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(cg.global_ns.namespace("esphome").using)
@ -347,6 +353,12 @@ async def to_code(config):
config[CONF_NAME_ADD_MAC_SUFFIX],
)
)
# Reserve space for components to avoid reallocation during registration
cg.add(
cg.RawStatement(f"App.reserve_components({len(CORE.component_ids)});"),
)
CORE.add_job(_add_platform_reserves)
CORE.add_job(_add_automations, config)

View File

@ -579,13 +579,13 @@ def new_Pvariable(id_: ID, *args: SafeExpType) -> Pvariable:
return Pvariable(id_, rhs)
def add(expression: Expression | Statement):
def add(expression: Expression | Statement, prepend: bool = False):
"""Add an expression to the codegen section.
After this is called, the given given expression will
show up in the setup() function after this has been called.
"""
CORE.add(expression)
CORE.add(expression, prepend)
def add_global(expression: SafeExpType | Statement, prepend: bool = False):