Optimize API connection memory with tagged pointers (#9203)

This commit is contained in:
J. Nick Koston 2025-06-26 03:55:12 +02:00 committed by GitHub
parent c74e5e0f04
commit 79e3d2b2d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 64 additions and 54 deletions

View File

@ -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,

View File

@ -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