From 52c297206a2841b58a5ccd9a4eed8b455c057492 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 12:01:06 -1000 Subject: [PATCH] [api] Restore single-buffer write_raw_ overload to keep inline failure path small Moving iovec construction into write_raw_fast_ caused an 8% regression by enlarging the inlined failure path. The 33-byte out-of-line wrapper keeps the hot inline path minimal. --- esphome/components/api/api_frame_helper.cpp | 11 +++++++++-- esphome/components/api/api_frame_helper.h | 6 +++--- esphome/components/api/api_frame_helper_noise.cpp | 3 +-- esphome/components/api/api_frame_helper_plaintext.cpp | 6 ++---- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index bac5ec1c148..248e0630454 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -112,8 +112,15 @@ APIError APIFrameHelper::drain_overflow_and_handle_errors_() { return APIError::OK; } -// Slow path: handles partial writes, errors, and overflow buffering. -// Called when the inline fast path in the header couldn't complete the write. +// Single-buffer write path: wraps in iovec and delegates. +APIError APIFrameHelper::write_raw_(const void *data, uint16_t len, ssize_t sent) { + struct iovec iov = {const_cast(data), len}; + return this->write_raw_(&iov, 1, len, sent); +} + +// Handles partial writes, errors, and overflow buffering. +// Called when the inline fast path in the header couldn't complete the write, +// or directly from cold paths (handshake, error handling). // sent == -1 means either the fast path write returned -1, or there was overflow backlog. APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent) { #ifdef HELPER_LOG_PACKETS diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 768cffdebbd..cba9d92c743 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -202,8 +202,7 @@ class APIFrameHelper { if (sent == static_cast(len)) [[likely]] return APIError::OK; } - struct iovec iov = {const_cast(data), len}; - return this->write_raw_(&iov, 1, len, sent); + return this->write_raw_(data, len, sent); } inline APIError ESPHOME_ALWAYS_INLINE write_raw_fast_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { ssize_t sent = -1; @@ -215,7 +214,8 @@ class APIFrameHelper { return this->write_raw_(iov, iovcnt, total_write_len, sent); } - // Slow path (out-of-line): handle partial writes, errors, overflow buffering + // Out-of-line write paths: handle partial writes, errors, overflow buffering + APIError write_raw_(const void *data, uint16_t len, ssize_t sent = -1); APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent = -1); // Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index f07f8635492..b1499c7557a 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -537,8 +537,7 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { header[2] = (uint8_t) len; if (len == 0) { - struct iovec iov = {header, 3}; - return this->write_raw_(&iov, 1, 3); + return this->write_raw_(header, 3); } struct iovec iov[2]; iov[0].iov_base = header; diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index 9144a0234bd..f561acfff94 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -219,13 +219,11 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { "Bad indicator byte"; char msg[INDICATOR_MSG_SIZE]; memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE); - struct iovec iov = {msg, INDICATOR_MSG_SIZE}; - this->write_raw_(&iov, 1, INDICATOR_MSG_SIZE); + this->write_raw_(msg, INDICATOR_MSG_SIZE); #else static const char MSG[] = "\x00" "Bad indicator byte"; - struct iovec iov = {const_cast(MSG), INDICATOR_MSG_SIZE}; - this->write_raw_(&iov, 1, INDICATOR_MSG_SIZE); + this->write_raw_(MSG, INDICATOR_MSG_SIZE); #endif } return aerr;