mirror of
https://github.com/esphome/esphome.git
synced 2025-07-29 06:36:45 +00:00
Optimize API connection memory with tagged pointers (#9203)
This commit is contained in:
parent
c74e5e0f04
commit
79e3d2b2d7
@ -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,22 +1901,24 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user