From 748ffa00f3f49e8e4f11b453ab1ac34497070029 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 25 Jun 2025 14:49:01 +0200 Subject: [PATCH 01/10] Optimize TemplatableValue memory --- esphome/core/automation.h | 67 +++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 02c9d44f16..e156818312 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -27,20 +27,67 @@ template class TemplatableValue { public: TemplatableValue() : type_(NONE) {} - template::value, int> = 0> - TemplatableValue(F value) : type_(VALUE), value_(std::move(value)) {} + template::value, int> = 0> TemplatableValue(F value) : type_(VALUE) { + new (&this->value_) T(std::move(value)); + } - template::value, int> = 0> - TemplatableValue(F f) : type_(LAMBDA), f_(f) {} + template::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) { + this->f_ = new std::function(std::move(f)); + } + + // Copy constructor + TemplatableValue(const TemplatableValue &other) : type_(other.type_) { + if (type_ == VALUE) { + new (&this->value_) T(other.value_); + } else if (type_ == LAMBDA) { + this->f_ = new std::function(*other.f_); + } + } + + // Move constructor + TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) { + if (type_ == VALUE) { + new (&this->value_) T(std::move(other.value_)); + } else if (type_ == LAMBDA) { + this->f_ = other.f_; + other.f_ = nullptr; + } + other.type_ = NONE; + } + + // Assignment operators + TemplatableValue &operator=(const TemplatableValue &other) { + if (this != &other) { + this->~TemplatableValue(); + new (this) TemplatableValue(other); + } + return *this; + } + + TemplatableValue &operator=(TemplatableValue &&other) noexcept { + if (this != &other) { + this->~TemplatableValue(); + new (this) TemplatableValue(std::move(other)); + } + return *this; + } + + ~TemplatableValue() { + if (type_ == VALUE) { + this->value_.~T(); + } else if (type_ == LAMBDA) { + delete this->f_; + } + } bool has_value() { return this->type_ != NONE; } T value(X... x) { if (this->type_ == LAMBDA) { - return this->f_(x...); + return (*this->f_)(x...); } // return value also when none - return this->value_; + return this->type_ == VALUE ? this->value_ : T{}; } optional optional_value(X... x) { @@ -58,14 +105,16 @@ template class TemplatableValue { } protected: - enum { + enum : uint8_t { NONE, VALUE, LAMBDA, } type_; - T value_{}; - std::function f_{}; + union { + T value_; + std::function *f_; + }; }; /** Base class for all automation conditions. From 915da9ae13e157cd5d2be9aedf47ab650fc38c9d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 25 Jun 2025 17:22:23 +0200 Subject: [PATCH 02/10] make the bot happy --- esphome/components/api/api_connection.h | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index dd76725c45..ea604e470e 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -485,6 +485,9 @@ class APIConnection : public APIServerConnection { // Optimized MessageCreator class using tagged pointer class MessageCreator { + // Ensure pointer alignment allows LSB tagging + static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging"); + public: // Constructor for function pointer MessageCreator(MessageCreatorPtr ptr) { @@ -502,14 +505,14 @@ class APIConnection : public APIServerConnection { // Destructor ~MessageCreator() { - if (is_string()) { + if (has_tagged_string_ptr()) { delete get_string_ptr(); } } // Copy constructor MessageCreator(const MessageCreator &other) { - if (other.is_string()) { + if (other.has_tagged_string_ptr()) { auto *str = new std::string(*other.get_string_ptr()); data_.tagged = reinterpret_cast(str) | 1; } else { @@ -524,11 +527,11 @@ class APIConnection : public APIServerConnection { MessageCreator &operator=(const MessageCreator &other) { if (this != &other) { // Clean up current string data if needed - if (is_string()) { + if (has_tagged_string_ptr()) { delete get_string_ptr(); } // Copy new data - if (other.is_string()) { + if (other.has_tagged_string_ptr()) { auto *str = new std::string(*other.get_string_ptr()); data_.tagged = reinterpret_cast(str) | 1; } else { @@ -541,7 +544,7 @@ class APIConnection : public APIServerConnection { MessageCreator &operator=(MessageCreator &&other) noexcept { if (this != &other) { // Clean up current string data if needed - if (is_string()) { + if (has_tagged_string_ptr()) { delete get_string_ptr(); } // Move data @@ -558,7 +561,7 @@ class APIConnection : public APIServerConnection { private: // Check if this contains a string pointer - bool is_string() const { return (data_.tagged & 1) != 0; } + bool has_tagged_string_ptr() const { return (data_.tagged & 1) != 0; } // Get the actual string pointer (clears the tag bit) std::string *get_string_ptr() const { return reinterpret_cast(data_.tagged & ~uintptr_t(1)); } From e20c6468d06e34ad8ab37bc0b18a5d8c2a6d8b80 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 25 Jun 2025 18:27:43 +0200 Subject: [PATCH 03/10] fix missed one --- 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 c58fd0c91f..4b1ab73654 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1910,7 +1910,7 @@ void APIConnection::process_batch_() { uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint16_t message_type) const { - if (is_string()) { + if (has_tagged_string_ptr()) { // Handle string-based messages switch (message_type) { #ifdef USE_EVENT From 9074ef792fd3b0c3c3750a5f10c4b6838d344be5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 25 Jun 2025 19:35:40 +0200 Subject: [PATCH 04/10] Reduce component_iterator memory usage --- esphome/core/component_iterator.cpp | 2 +- esphome/core/component_iterator.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/core/component_iterator.cpp b/esphome/core/component_iterator.cpp index da593340c1..03c8fb44f9 100644 --- a/esphome/core/component_iterator.cpp +++ b/esphome/core/component_iterator.cpp @@ -375,7 +375,7 @@ void ComponentIterator::advance() { } if (advance_platform) { - this->state_ = static_cast(static_cast(this->state_) + 1); + this->state_ = static_cast(static_cast(this->state_) + 1); this->at_ = 0; } else if (success) { this->at_++; diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index 9e187f6c57..c7cebfd178 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -93,7 +93,7 @@ class ComponentIterator { virtual bool on_end(); protected: - enum class IteratorState { + enum class IteratorState : uint8_t { NONE = 0, BEGIN, #ifdef USE_BINARY_SENSOR @@ -167,7 +167,7 @@ class ComponentIterator { #endif MAX, } state_{IteratorState::NONE}; - size_t at_{0}; + uint16_t at_{0}; bool include_internal_{false}; }; From 83884970384ba4a2923352100ad3b822fd364e73 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 25 Jun 2025 23:18:50 +0200 Subject: [PATCH 05/10] tidy issues --- esphome/components/api/api_connection.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index ea604e470e..0a1b1eeebc 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -505,15 +505,15 @@ class APIConnection : public APIServerConnection { // Destructor ~MessageCreator() { - if (has_tagged_string_ptr()) { - delete get_string_ptr(); + if (has_tagged_string_ptr_()) { + delete get_string_ptr_(); } } // Copy constructor MessageCreator(const MessageCreator &other) { - if (other.has_tagged_string_ptr()) { - auto *str = new std::string(*other.get_string_ptr()); + if (other.has_tagged_string_ptr_()) { + auto *str = new std::string(*other.get_string_ptr_()); data_.tagged = reinterpret_cast(str) | 1; } else { data_ = other.data_; @@ -527,12 +527,12 @@ class APIConnection : public APIServerConnection { MessageCreator &operator=(const MessageCreator &other) { if (this != &other) { // Clean up current string data if needed - if (has_tagged_string_ptr()) { - delete get_string_ptr(); + if (has_tagged_string_ptr_()) { + delete get_string_ptr_(); } // Copy new data - if (other.has_tagged_string_ptr()) { - auto *str = new std::string(*other.get_string_ptr()); + if (other.has_tagged_string_ptr_()) { + auto *str = new std::string(*other.get_string_ptr_()); data_.tagged = reinterpret_cast(str) | 1; } else { data_ = other.data_; @@ -544,8 +544,8 @@ class APIConnection : public APIServerConnection { MessageCreator &operator=(MessageCreator &&other) noexcept { if (this != &other) { // Clean up current string data if needed - if (has_tagged_string_ptr()) { - delete get_string_ptr(); + if (has_tagged_string_ptr_()) { + delete get_string_ptr_(); } // Move data data_ = other.data_; @@ -561,10 +561,10 @@ class APIConnection : public APIServerConnection { private: // Check if this contains a string pointer - bool has_tagged_string_ptr() const { return (data_.tagged & 1) != 0; } + bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; } // Get the actual string pointer (clears the tag bit) - std::string *get_string_ptr() const { return reinterpret_cast(data_.tagged & ~uintptr_t(1)); } + std::string *get_string_ptr_() const { return reinterpret_cast(data_.tagged & ~uintptr_t(1)); } union { MessageCreatorPtr ptr; From 6b5b0815d72a122165323a77894e0ede4136f5b9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 25 Jun 2025 23:26:57 +0200 Subject: [PATCH 06/10] tidy issues --- esphome/components/api/api_connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 4b1ab73654..06ca3600ed 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1910,13 +1910,13 @@ void APIConnection::process_batch_() { uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint16_t message_type) const { - if (has_tagged_string_ptr()) { + if (has_tagged_string_ptr_()) { // Handle string-based messages switch (message_type) { #ifdef USE_EVENT case EventResponse::MESSAGE_TYPE: { auto *e = static_cast(entity); - return APIConnection::try_send_event_response(e, *get_string_ptr(), conn, remaining_size, is_single); + return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single); } #endif default: From a7e0bf9013d4f19f53a74948b4e02c0aec8e6838 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 25 Jun 2025 23:53:22 +0200 Subject: [PATCH 07/10] tweak --- esphome/core/component_iterator.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index c7cebfd178..4b41872db7 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -93,6 +93,8 @@ class ComponentIterator { virtual bool on_end(); protected: + // Iterates over all ESPHome entities (sensors, switches, lights, etc.) + // Supports up to 256 entity types and up to 65,535 entities of each type enum class IteratorState : uint8_t { NONE = 0, BEGIN, @@ -167,7 +169,7 @@ class ComponentIterator { #endif MAX, } state_{IteratorState::NONE}; - uint16_t at_{0}; + uint16_t at_{0}; // Supports up to 65,535 entities per type bool include_internal_{false}; }; From 4b5424f69527b0dfe4eddc35cb74d658f0d574d4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 26 Jun 2025 00:08:15 +0200 Subject: [PATCH 08/10] nolint --- esphome/components/api/api_connection.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 0a1b1eeebc..40f60cecc5 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -564,7 +564,9 @@ class APIConnection : public APIServerConnection { bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; } // Get the actual string pointer (clears the tag bit) - std::string *get_string_ptr_() const { return reinterpret_cast(data_.tagged & ~uintptr_t(1)); } + std::string *get_string_ptr_() const { + return reinterpret_cast(data_.tagged & ~uintptr_t(1)); + } // NOLINT(performance-no-int-to-ptr) union { MessageCreatorPtr ptr; From 78d84644c986ac342c1d4dfa4f428cfce35b7aff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 26 Jun 2025 00:24:12 +0200 Subject: [PATCH 09/10] lint --- esphome/components/api/api_connection.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 40f60cecc5..23ebc6b881 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -565,8 +565,8 @@ class APIConnection : public APIServerConnection { // Get the actual string pointer (clears the tag bit) std::string *get_string_ptr_() const { - return reinterpret_cast(data_.tagged & ~uintptr_t(1)); - } // NOLINT(performance-no-int-to-ptr) + return reinterpret_cast(data_.tagged & ~uintptr_t(1)); // NOLINT(performance-no-int-to-ptr) + } union { MessageCreatorPtr ptr; From 5e3ec2d34b545e92b11a171731f8a37aafb27c56 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 26 Jun 2025 00:24:53 +0200 Subject: [PATCH 10/10] lint --- esphome/components/api/api_connection.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 23ebc6b881..e872711e95 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -565,7 +565,8 @@ class APIConnection : public APIServerConnection { // Get the actual string pointer (clears the tag bit) std::string *get_string_ptr_() const { - return reinterpret_cast(data_.tagged & ~uintptr_t(1)); // NOLINT(performance-no-int-to-ptr) + // NOLINTNEXTLINE(performance-no-int-to-ptr) + return reinterpret_cast(data_.tagged & ~uintptr_t(1)); } union {