From 5553962eaebca24a78b48947d8ccb7b6071ef147 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 3 Apr 2026 23:16:56 -1000 Subject: [PATCH] fix bloat --- esphome/components/api/api_connection.cpp | 8 +++- esphome/components/api/api_connection.h | 48 ++++++----------------- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index bddfe759e1..2ccb597bbc 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -398,7 +398,7 @@ uint16_t APIConnection::fill_and_encode_entity_info(EntityBase *entity, InfoResp #ifdef USE_DEVICES msg.device_id = entity->get_device_id(); #endif - return encode_to_buffer(size_fn(&msg), encode_fn, &msg, conn, remaining_size); + return encode_to_buffer_slow_(size_fn(&msg), encode_fn, &msg, conn, remaining_size); } uint16_t APIConnection::fill_and_encode_entity_info_with_device_class(EntityBase *entity, InfoResponseProtoMessage &msg, @@ -1998,7 +1998,11 @@ bool APIConnection::send_message_(uint32_t payload_size, uint8_t message_type, M } // encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE) -// encode_empty_to_buffer is defined inline in api_connection.h +// Noinline version for cold paths — single shared copy +uint16_t APIConnection::encode_to_buffer_slow_(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, + APIConnection *conn, uint32_t remaining_size) { + return encode_to_buffer(calculated_size, encode_fn, msg, conn, remaining_size); +} bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 0fb7025608..6fbf24c215 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -402,9 +402,8 @@ class APIConnection final : public APIServerConnectionBase { // Non-template buffer management for send_message bool send_message_(uint32_t payload_size, uint8_t message_type, MessageEncodeFn encode_fn, const void *msg); - // Non-template buffer management for batch encoding. - // ALWAYS_INLINE so it merges into each call site — the compiler can then - // inline the encode_fn callback and eliminate the indirect call. + // Core batch encoding logic. Computes header size, checks fit, resizes buffer, encodes. + // ALWAYS_INLINE so the compiler can devirtualize encode_fn at hot call sites. static inline uint16_t ESPHOME_ALWAYS_INLINE encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, APIConnection *conn, uint32_t remaining_size) { @@ -440,43 +439,20 @@ class APIConnection final : public APIServerConnectionBase { return total_calculated_size; } - // Specialized path for zero-payload messages (ping, disconnect, list_info_done). - // No payload means no encode callback and no payload resize needed. - // Noinline — the three callers share a single copy via a call. - __attribute__((noinline)) static uint16_t encode_empty_to_buffer(const void *msg, uint8_t message_type, - APIConnection *conn, uint32_t remaining_size) { -#ifdef HAS_PROTO_MESSAGE_DUMP - if (conn->flags_.log_only_mode) { - conn->log_send_message_(msg); - return 1; - } -#endif - const uint8_t footer_size = conn->helper_->frame_footer_size(); - bool first = conn->flags_.batch_first_message; - if (first) { - conn->flags_.batch_first_message = false; - conn->batch_header_size_ = conn->helper_->frame_header_padding(); - } else { - conn->batch_header_size_ = conn->helper_->frame_header_size(0, message_type); - } - uint16_t total = conn->batch_header_size_ + footer_size; - if (total > remaining_size) - return 0; - if (!first) { - // Non-first: grow buffer by header + footer (no payload) - auto &shared_buf = conn->parent_->get_shared_buffer_ref(); - shared_buf.resize(shared_buf.size() + total); - } - // First message: padding already in buffer from prepare_first_message_buffer - return total; - } + // Noinline version of encode_to_buffer for cold paths (entity info, zero-payload messages). + // All cold callers share this single copy instead of each getting an ALWAYS_INLINE expansion. + static uint16_t encode_to_buffer_slow_(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, + APIConnection *conn, uint32_t remaining_size); - // Thin template wrapper — computes size, delegates buffer work to non-template helper + // Thin template wrapper — uses noinline encode_to_buffer_slow_ since + // encode_message_to_buffer callers are cold paths (zero-payload control messages). + // Hot paths (state/info) go through fill_and_encode_entity_state/info instead. template static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) { + conn->batch_message_type_ = T::MESSAGE_TYPE; if constexpr (T::ESTIMATED_SIZE == 0) { - return encode_empty_to_buffer(&msg, T::MESSAGE_TYPE, conn, remaining_size); + return encode_to_buffer_slow_(0, &encode_msg_noop, &msg, conn, remaining_size); } else { - return encode_to_buffer(msg.calculate_size(), &proto_encode_msg, &msg, conn, remaining_size); + return encode_to_buffer_slow_(msg.calculate_size(), &proto_encode_msg, &msg, conn, remaining_size); } }