diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index bd3de02895..4c41fcae55 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1915,7 +1915,7 @@ uint16_t APIConnection::encode_to_buffer(uint32_t calculated_size, MessageEncode if (total_calculated_size > remaining_size) return 0; // Doesn't fit - std::vector &shared_buf = conn->parent_->get_shared_buffer_ref(); + auto &shared_buf = conn->parent_->get_shared_buffer_ref(); if (conn->flags_.batch_first_message) { // First message - buffer already prepared by caller, just clear flag @@ -2083,7 +2083,7 @@ void APIConnection::process_batch_() { // Separated from process_batch_() so the single-message fast path gets a minimal // stack frame without the MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo) array. -void APIConnection::process_batch_multi_(std::vector &shared_buf, size_t num_items, uint8_t header_padding, +void APIConnection::process_batch_multi_(ProtoByteBuffer &shared_buf, size_t num_items, uint8_t header_padding, uint8_t footer_size) { // Ensure MessageInfo remains trivially destructible for our placement new approach static_assert(std::is_trivially_destructible::value, diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index b075bc83ab..cb812bd127 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -278,7 +278,7 @@ class APIConnection final : public APIServerConnectionBase { } } - void prepare_first_message_buffer(std::vector &shared_buf, size_t header_padding, size_t total_size) { + void prepare_first_message_buffer(ProtoByteBuffer &shared_buf, size_t header_padding, size_t total_size) { shared_buf.clear(); // Reserve space for header padding + message + footer // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext) @@ -289,7 +289,7 @@ class APIConnection final : public APIServerConnectionBase { } // Convenience overload - computes frame overhead internally - void prepare_first_message_buffer(std::vector &shared_buf, size_t payload_size) { + void prepare_first_message_buffer(ProtoByteBuffer &shared_buf, size_t payload_size) { const uint8_t header_padding = this->helper_->frame_header_padding(); const uint8_t footer_size = this->helper_->frame_footer_size(); this->prepare_first_message_buffer(shared_buf, header_padding, payload_size + header_padding + footer_size); @@ -669,8 +669,8 @@ class APIConnection final : public APIServerConnectionBase { bool schedule_batch_(); void process_batch_(); - void process_batch_multi_(std::vector &shared_buf, size_t num_items, uint8_t header_padding, - uint8_t footer_size) __attribute__((noinline)); + void process_batch_multi_(ProtoByteBuffer &shared_buf, size_t num_items, uint8_t header_padding, uint8_t footer_size) + __attribute__((noinline)); void clear_batch_() { this->deferred_batch_.clear(); this->flags_.batch_scheduled = false; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index e5f371d8a1..136175b1e5 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -65,7 +65,7 @@ class APIServer : public Component, void set_max_connections(uint8_t max_connections) { this->max_connections_ = max_connections; } // Get reference to shared buffer for API connections - std::vector &get_shared_buffer_ref() { return shared_write_buffer_; } + ProtoByteBuffer &get_shared_buffer_ref() { return shared_write_buffer_; } #ifdef USE_API_NOISE bool save_noise_psk(psk_t psk, bool make_active = true); @@ -276,7 +276,7 @@ class APIServer : public Component, // Not pre-allocated: all send paths call prepare_first_message_buffer() which // reserves the exact needed size. Pre-allocating here would cause heap fragmentation // since the buffer would almost always reallocate on first use. - std::vector shared_write_buffer_; + ProtoByteBuffer shared_write_buffer_; #ifdef USE_API_HOMEASSISTANT_STATES std::vector state_subs_; #endif diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index bbdd11b29d..48e5497e81 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -8,6 +8,7 @@ #include #include +#include #include #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE @@ -235,11 +236,49 @@ class Proto32Bit { // NOTE: Proto64Bit class removed - wire type 1 (64-bit fixed) not supported +/// Helper to use make_unique_for_overwrite where available (skips zero-fill), +/// falling back to make_unique on ESP8266's older GCC. +inline std::unique_ptr make_buffer(size_t n) { +#ifdef USE_ESP8266 + return std::make_unique(n); +#else + return std::make_unique_for_overwrite(n); +#endif +} + +/// Byte buffer that skips zero-initialization on resize(). +/// Used as the shared protobuf write buffer to avoid wasted memset +/// on bytes that will be overwritten by the encoder. +class ProtoByteBuffer { + public: + void clear() { this->size_ = 0; } + void reserve(size_t n) { + if (n > this->capacity_) { + auto new_data = make_buffer(n); + if (this->size_) + std::memcpy(new_data.get(), this->data_.get(), this->size_); + this->data_ = std::move(new_data); + this->capacity_ = n; + } + } + void resize(size_t n) { + this->reserve(n); + this->size_ = n; // no zero-fill + } + uint8_t *data() { return this->data_.get(); } + const uint8_t *data() const { return this->data_.get(); } + size_t size() const { return this->size_; } + + protected: + std::unique_ptr data_; + size_t size_{0}; + size_t capacity_{0}; +}; + class ProtoWriteBuffer { public: - ProtoWriteBuffer(std::vector *buffer) : buffer_(buffer), pos_(buffer->data() + buffer->size()) {} - ProtoWriteBuffer(std::vector *buffer, size_t write_pos) - : buffer_(buffer), pos_(buffer->data() + write_pos) {} + ProtoWriteBuffer(ProtoByteBuffer *buffer) : buffer_(buffer), pos_(buffer->data() + buffer->size()) {} + ProtoWriteBuffer(ProtoByteBuffer *buffer, size_t write_pos) : buffer_(buffer), pos_(buffer->data() + write_pos) {} void encode_varint_raw(uint32_t value) { while (value > 0x7F) { this->debug_check_bounds_(1); @@ -375,7 +414,7 @@ class ProtoWriteBuffer { // Non-template core for encode_optional_sub_message. void encode_optional_sub_message(uint32_t field_id, uint32_t nested_size, const void *value, void (*encode_fn)(const void *, ProtoWriteBuffer &)); - std::vector *get_buffer() const { return buffer_; } + ProtoByteBuffer *get_buffer() const { return buffer_; } protected: #ifdef ESPHOME_DEBUG_API @@ -385,7 +424,7 @@ class ProtoWriteBuffer { void debug_check_bounds_([[maybe_unused]] size_t bytes) {} #endif - std::vector *buffer_; + ProtoByteBuffer *buffer_; uint8_t *pos_; };