mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Optimize API connection memory with tagged pointers (#9203)
This commit is contained in:
		| @@ -1432,7 +1432,7 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe | |||||||
|  |  | ||||||
| #ifdef USE_EVENT | #ifdef USE_EVENT | ||||||
| void APIConnection::send_event(event::Event *event, const std::string &event_type) { | void APIConnection::send_event(event::Event *event, const std::string &event_type) { | ||||||
|   this->schedule_message_(event, MessageCreator(event_type, EventResponse::MESSAGE_TYPE), EventResponse::MESSAGE_TYPE); |   this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| void APIConnection::send_event_info(event::Event *event) { | void APIConnection::send_event_info(event::Event *event) { | ||||||
|   this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE); |   this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE); | ||||||
| @@ -1787,7 +1787,8 @@ void APIConnection::process_batch_() { | |||||||
|     const auto &item = this->deferred_batch_.items[0]; |     const auto &item = this->deferred_batch_.items[0]; | ||||||
|  |  | ||||||
|     // Let the creator calculate size and encode if it fits |     // Let the creator calculate size and encode if it fits | ||||||
|     uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true); |     uint16_t payload_size = | ||||||
|  |         item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); | ||||||
|  |  | ||||||
|     if (payload_size > 0 && |     if (payload_size > 0 && | ||||||
|         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { |         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { | ||||||
| @@ -1837,7 +1838,7 @@ void APIConnection::process_batch_() { | |||||||
|   for (const auto &item : this->deferred_batch_.items) { |   for (const auto &item : this->deferred_batch_.items) { | ||||||
|     // Try to encode message |     // Try to encode message | ||||||
|     // The creator will calculate overhead to determine if the message fits |     // The creator will calculate overhead to determine if the message fits | ||||||
|     uint16_t payload_size = item.creator(item.entity, this, remaining_size, false); |     uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); | ||||||
|  |  | ||||||
|     if (payload_size == 0) { |     if (payload_size == 0) { | ||||||
|       // Message won't fit, stop processing |       // Message won't fit, stop processing | ||||||
| @@ -1900,21 +1901,23 @@ void APIConnection::process_batch_() { | |||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                                    bool is_single) const { |                                                    bool is_single, uint16_t message_type) const { | ||||||
|   switch (message_type_) { |   if (has_tagged_string_ptr_()) { | ||||||
|     case 0:  // Function pointer |     // Handle string-based messages | ||||||
|       return data_.ptr(entity, conn, remaining_size, is_single); |     switch (message_type) { | ||||||
|  |  | ||||||
| #ifdef USE_EVENT | #ifdef USE_EVENT | ||||||
|     case EventResponse::MESSAGE_TYPE: { |       case EventResponse::MESSAGE_TYPE: { | ||||||
|       auto *e = static_cast<event::Event *>(entity); |         auto *e = static_cast<event::Event *>(entity); | ||||||
|       return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single); |         return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single); | ||||||
|     } |       } | ||||||
| #endif | #endif | ||||||
|  |       default: | ||||||
|     default: |         // Should not happen, return 0 to indicate no message | ||||||
|       // Should not happen, return 0 to indicate no message |         return 0; | ||||||
|       return 0; |     } | ||||||
|  |   } else { | ||||||
|  |     // Function pointer case | ||||||
|  |     return data_.ptr(entity, conn, remaining_size, is_single); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -483,55 +483,57 @@ class APIConnection : public APIServerConnection { | |||||||
|   // Function pointer type for message encoding |   // Function pointer type for message encoding | ||||||
|   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); |   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); | ||||||
|  |  | ||||||
|   // Optimized MessageCreator class using union dispatch |   // Optimized MessageCreator class using tagged pointer | ||||||
|   class MessageCreator { |   class MessageCreator { | ||||||
|  |     // Ensure pointer alignment allows LSB tagging | ||||||
|  |     static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging"); | ||||||
|  |  | ||||||
|    public: |    public: | ||||||
|     // Constructor for function pointer (message_type = 0) |     // Constructor for function pointer | ||||||
|     MessageCreator(MessageCreatorPtr ptr) : message_type_(0) { data_.ptr = ptr; } |     MessageCreator(MessageCreatorPtr ptr) { | ||||||
|  |       // Function pointers are always aligned, so LSB is 0 | ||||||
|  |       data_.ptr = ptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Constructor for string state capture |     // Constructor for string state capture | ||||||
|     MessageCreator(const std::string &value, uint16_t msg_type) : message_type_(msg_type) { |     explicit MessageCreator(const std::string &str_value) { | ||||||
|       data_.string_ptr = new std::string(value); |       // Allocate string and tag the pointer | ||||||
|  |       auto *str = new std::string(str_value); | ||||||
|  |       // Set LSB to 1 to indicate string pointer | ||||||
|  |       data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Destructor |     // Destructor | ||||||
|     ~MessageCreator() { |     ~MessageCreator() { | ||||||
|       // Clean up string data for string-based message types |       if (has_tagged_string_ptr_()) { | ||||||
|       if (uses_string_data_()) { |         delete get_string_ptr_(); | ||||||
|         delete data_.string_ptr; |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Copy constructor |     // Copy constructor | ||||||
|     MessageCreator(const MessageCreator &other) : message_type_(other.message_type_) { |     MessageCreator(const MessageCreator &other) { | ||||||
|       if (message_type_ == 0) { |       if (other.has_tagged_string_ptr_()) { | ||||||
|         data_.ptr = other.data_.ptr; |         auto *str = new std::string(*other.get_string_ptr_()); | ||||||
|       } else if (uses_string_data_()) { |         data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||||
|         data_.string_ptr = new std::string(*other.data_.string_ptr); |  | ||||||
|       } else { |       } else { | ||||||
|         data_ = other.data_;  // For POD types |         data_ = other.data_; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Move constructor |     // Move constructor | ||||||
|     MessageCreator(MessageCreator &&other) noexcept : data_(other.data_), message_type_(other.message_type_) { |     MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; } | ||||||
|       other.message_type_ = 0;  // Reset other to function pointer type |  | ||||||
|       other.data_.ptr = nullptr; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Assignment operators (needed for batch deduplication) |     // Assignment operators (needed for batch deduplication) | ||||||
|     MessageCreator &operator=(const MessageCreator &other) { |     MessageCreator &operator=(const MessageCreator &other) { | ||||||
|       if (this != &other) { |       if (this != &other) { | ||||||
|         // Clean up current string data if needed |         // Clean up current string data if needed | ||||||
|         if (uses_string_data_()) { |         if (has_tagged_string_ptr_()) { | ||||||
|           delete data_.string_ptr; |           delete get_string_ptr_(); | ||||||
|         } |         } | ||||||
|         // Copy new data |         // Copy new data | ||||||
|         message_type_ = other.message_type_; |         if (other.has_tagged_string_ptr_()) { | ||||||
|         if (other.message_type_ == 0) { |           auto *str = new std::string(*other.get_string_ptr_()); | ||||||
|           data_.ptr = other.data_.ptr; |           data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||||
|         } else if (other.uses_string_data_()) { |  | ||||||
|           data_.string_ptr = new std::string(*other.data_.string_ptr); |  | ||||||
|         } else { |         } else { | ||||||
|           data_ = other.data_; |           data_ = other.data_; | ||||||
|         } |         } | ||||||
| @@ -542,30 +544,35 @@ class APIConnection : public APIServerConnection { | |||||||
|     MessageCreator &operator=(MessageCreator &&other) noexcept { |     MessageCreator &operator=(MessageCreator &&other) noexcept { | ||||||
|       if (this != &other) { |       if (this != &other) { | ||||||
|         // Clean up current string data if needed |         // Clean up current string data if needed | ||||||
|         if (uses_string_data_()) { |         if (has_tagged_string_ptr_()) { | ||||||
|           delete data_.string_ptr; |           delete get_string_ptr_(); | ||||||
|         } |         } | ||||||
|         // Move data |         // Move data | ||||||
|         message_type_ = other.message_type_; |  | ||||||
|         data_ = other.data_; |         data_ = other.data_; | ||||||
|         // Reset other to safe state |         // Reset other to safe state | ||||||
|         other.message_type_ = 0; |  | ||||||
|         other.data_.ptr = nullptr; |         other.data_.ptr = nullptr; | ||||||
|       } |       } | ||||||
|       return *this; |       return *this; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Call operator |     // Call operator - now accepts message_type as parameter | ||||||
|     uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) const; |     uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, | ||||||
|  |                         uint16_t message_type) const; | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     // Helper to check if this message type uses heap-allocated strings |     // Check if this contains a string pointer | ||||||
|     bool uses_string_data_() const { return message_type_ == EventResponse::MESSAGE_TYPE; } |     bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; } | ||||||
|     union CreatorData { |  | ||||||
|       MessageCreatorPtr ptr;    // 8 bytes |     // Get the actual string pointer (clears the tag bit) | ||||||
|       std::string *string_ptr;  // 8 bytes |     std::string *get_string_ptr_() const { | ||||||
|     } data_;                    // 8 bytes |       // NOLINTNEXTLINE(performance-no-int-to-ptr) | ||||||
|     uint16_t message_type_;     // 2 bytes (0 = function ptr, >0 = state capture) |       return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     union { | ||||||
|  |       MessageCreatorPtr ptr; | ||||||
|  |       uintptr_t tagged; | ||||||
|  |     } data_;  // 4 bytes on 32-bit | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   // Generic batching mechanism for both state updates and entity info |   // Generic batching mechanism for both state updates and entity info | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user