From fc91a4d7a38f7f56a9f3d402cadce67d057e4eed Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 20:10:44 -0600 Subject: [PATCH 1/5] Extract iterator switch from loop() into process_active_iterator_() The iterator switch only runs during initial connection handshake. In steady state active_iterator_ is always NONE, so the entire switch block is cold code that wastes icache in the hot loop path. --- esphome/components/api/api_connection.cpp | 64 +++++++++++++---------- esphome/components/api/api_connection.h | 4 ++ 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 96a6ed7ae9..c0d3d08246 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -219,35 +219,8 @@ void APIConnection::loop() { this->process_batch_(); } - switch (this->active_iterator_) { - case ActiveIterator::LIST_ENTITIES: - if (this->iterator_storage_.list_entities.completed()) { - this->destroy_active_iterator_(); - if (this->flags_.state_subscription) { - this->begin_iterator_(ActiveIterator::INITIAL_STATE); - } - } else { - this->process_iterator_batch_(this->iterator_storage_.list_entities); - } - break; - case ActiveIterator::INITIAL_STATE: - if (this->iterator_storage_.initial_state.completed()) { - this->destroy_active_iterator_(); - // Process any remaining batched messages immediately - if (!this->deferred_batch_.empty()) { - this->process_batch_(); - } - // Now that everything is sent, enable immediate sending for future state changes - this->flags_.should_try_send_immediately = true; - // Release excess memory from buffers that grew during initial sync - this->deferred_batch_.release_buffer(); - this->helper_->release_buffers(); - } else { - this->process_iterator_batch_(this->iterator_storage_.initial_state); - } - break; - case ActiveIterator::NONE: - break; + if (this->active_iterator_ != ActiveIterator::NONE) { + this->process_active_iterator_(); } if (this->flags_.sent_ping) { @@ -283,6 +256,39 @@ void APIConnection::loop() { #endif } +void APIConnection::process_active_iterator_() { + switch (this->active_iterator_) { + case ActiveIterator::LIST_ENTITIES: + if (this->iterator_storage_.list_entities.completed()) { + this->destroy_active_iterator_(); + if (this->flags_.state_subscription) { + this->begin_iterator_(ActiveIterator::INITIAL_STATE); + } + } else { + this->process_iterator_batch_(this->iterator_storage_.list_entities); + } + break; + case ActiveIterator::INITIAL_STATE: + if (this->iterator_storage_.initial_state.completed()) { + this->destroy_active_iterator_(); + // Process any remaining batched messages immediately + if (!this->deferred_batch_.empty()) { + this->process_batch_(); + } + // Now that everything is sent, enable immediate sending for future state changes + this->flags_.should_try_send_immediately = true; + // Release excess memory from buffers that grew during initial sync + this->deferred_batch_.release_buffer(); + this->helper_->release_buffers(); + } else { + this->process_iterator_batch_(this->iterator_storage_.initial_state); + } + break; + case ActiveIterator::NONE: + break; + } +} + void APIConnection::process_iterator_batch_(ComponentIterator &iterator) { size_t initial_size = this->deferred_batch_.size(); size_t max_batch = this->get_max_batch_size_(); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 3115507fa3..55b5402323 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -364,6 +364,10 @@ class APIConnection final : public APIServerConnectionBase { return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY; } + // Process active iterator (list_entities/initial_state) during connection setup. + // Extracted from loop() — only runs during initial handshake, NONE in steady state. + void __attribute__((noinline)) process_active_iterator_(); + // Helper method to process multiple entities from an iterator in a batch. // Takes ComponentIterator base class reference to avoid duplicate template instantiations. void process_iterator_batch_(ComponentIterator &iterator); From 2c3a92db971ca8e6559215504616dbcfaea16d2d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 20:11:45 -0600 Subject: [PATCH 2/5] Add forward declaration for ComponentIterator in api_connection.h --- esphome/components/api/api_connection.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 55b5402323..c69e5eb9ed 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -15,6 +15,10 @@ #include #include +namespace esphome { +class ComponentIterator; +} // namespace esphome + namespace esphome::api { // Keepalive timeout in milliseconds From b4741ade0db26d33af4d2d98f271c5807d72d1ae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 20:13:57 -0600 Subject: [PATCH 3/5] Remove unreachable NONE case from process_active_iterator_() --- esphome/components/api/api_connection.cpp | 50 +++++++++++------------ 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index c0d3d08246..2b2b3a992b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -257,35 +257,31 @@ void APIConnection::loop() { } void APIConnection::process_active_iterator_() { - switch (this->active_iterator_) { - case ActiveIterator::LIST_ENTITIES: - if (this->iterator_storage_.list_entities.completed()) { - this->destroy_active_iterator_(); - if (this->flags_.state_subscription) { - this->begin_iterator_(ActiveIterator::INITIAL_STATE); - } - } else { - this->process_iterator_batch_(this->iterator_storage_.list_entities); + // Caller ensures active_iterator_ != NONE + if (this->active_iterator_ == ActiveIterator::LIST_ENTITIES) { + if (this->iterator_storage_.list_entities.completed()) { + this->destroy_active_iterator_(); + if (this->flags_.state_subscription) { + this->begin_iterator_(ActiveIterator::INITIAL_STATE); } - break; - case ActiveIterator::INITIAL_STATE: - if (this->iterator_storage_.initial_state.completed()) { - this->destroy_active_iterator_(); - // Process any remaining batched messages immediately - if (!this->deferred_batch_.empty()) { - this->process_batch_(); - } - // Now that everything is sent, enable immediate sending for future state changes - this->flags_.should_try_send_immediately = true; - // Release excess memory from buffers that grew during initial sync - this->deferred_batch_.release_buffer(); - this->helper_->release_buffers(); - } else { - this->process_iterator_batch_(this->iterator_storage_.initial_state); + } else { + this->process_iterator_batch_(this->iterator_storage_.list_entities); + } + } else { + if (this->iterator_storage_.initial_state.completed()) { + this->destroy_active_iterator_(); + // Process any remaining batched messages immediately + if (!this->deferred_batch_.empty()) { + this->process_batch_(); } - break; - case ActiveIterator::NONE: - break; + // Now that everything is sent, enable immediate sending for future state changes + this->flags_.should_try_send_immediately = true; + // Release excess memory from buffers that grew during initial sync + this->deferred_batch_.release_buffer(); + this->helper_->release_buffers(); + } else { + this->process_iterator_batch_(this->iterator_storage_.initial_state); + } } } From 041c43fb32c803534ec33b956c0798091d780ddf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 20:14:54 -0600 Subject: [PATCH 4/5] Add INITIAL_STATE comment to else branch --- esphome/components/api/api_connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2b2b3a992b..329a341b21 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -267,7 +267,7 @@ void APIConnection::process_active_iterator_() { } else { this->process_iterator_batch_(this->iterator_storage_.list_entities); } - } else { + } else { // INITIAL_STATE if (this->iterator_storage_.initial_state.completed()) { this->destroy_active_iterator_(); // Process any remaining batched messages immediately From c53baf70c702cb8d08605d188d7c2814081ac9fb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 20:16:09 -0600 Subject: [PATCH 5/5] Collapse else chain in process_active_iterator_() with early returns --- esphome/components/api/api_connection.cpp | 41 ++++++++++++----------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 329a341b21..cdb8246c45 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -259,30 +259,31 @@ void APIConnection::loop() { void APIConnection::process_active_iterator_() { // Caller ensures active_iterator_ != NONE if (this->active_iterator_ == ActiveIterator::LIST_ENTITIES) { - if (this->iterator_storage_.list_entities.completed()) { - this->destroy_active_iterator_(); - if (this->flags_.state_subscription) { - this->begin_iterator_(ActiveIterator::INITIAL_STATE); - } - } else { + if (!this->iterator_storage_.list_entities.completed()) { this->process_iterator_batch_(this->iterator_storage_.list_entities); + return; } - } else { // INITIAL_STATE - if (this->iterator_storage_.initial_state.completed()) { - this->destroy_active_iterator_(); - // Process any remaining batched messages immediately - if (!this->deferred_batch_.empty()) { - this->process_batch_(); - } - // Now that everything is sent, enable immediate sending for future state changes - this->flags_.should_try_send_immediately = true; - // Release excess memory from buffers that grew during initial sync - this->deferred_batch_.release_buffer(); - this->helper_->release_buffers(); - } else { - this->process_iterator_batch_(this->iterator_storage_.initial_state); + this->destroy_active_iterator_(); + if (this->flags_.state_subscription) { + this->begin_iterator_(ActiveIterator::INITIAL_STATE); } + return; } + // INITIAL_STATE + if (!this->iterator_storage_.initial_state.completed()) { + this->process_iterator_batch_(this->iterator_storage_.initial_state); + return; + } + this->destroy_active_iterator_(); + // Process any remaining batched messages immediately + if (!this->deferred_batch_.empty()) { + this->process_batch_(); + } + // Now that everything is sent, enable immediate sending for future state changes + this->flags_.should_try_send_immediately = true; + // Release excess memory from buffers that grew during initial sync + this->deferred_batch_.release_buffer(); + this->helper_->release_buffers(); } void APIConnection::process_iterator_batch_(ComponentIterator &iterator) {