From 3a76f9d5d223e65cddecf5b46a874400fc213c47 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Mar 2026 19:51:55 -1000 Subject: [PATCH] [api] Merge varint length + memcpy into single pos scope in encode_string Previously encode_string called encode_varint_raw(len) then encode_raw(data, len) as separate methods, each with their own __restrict__ pos scope. This caused a redundant store-load pair of pos_ between the two operations. Inline the length varint write and memcpy under a single local pos variable so the compiler can keep pos_ in a register across both operations. Eliminates one load-store pair per string encode. --- esphome/components/api/proto.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 7de630daeb2..a669707a1a4 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -288,11 +288,17 @@ class ProtoWriteBuffer { return; this->encode_field_raw(field_id, 2); // type 2: Length-delimited string - this->encode_varint_raw(len); - // Direct memcpy into pre-sized buffer — avoids push_back() per-byte capacity checks - // and vector::insert() iterator overhead. ~10-11x faster for 16-32 byte strings. - this->debug_check_bounds_(len); + // Inline the length varint + memcpy under a single __restrict__ pos + // to avoid a store-load pair between encode_varint_raw and encode_raw. + this->debug_check_bounds_(1 + len); uint8_t *__restrict__ pos = this->pos_; + if (len < 128) [[likely]] { + *pos++ = static_cast(len); + } else { + // Length >= 128: use slow path for the length varint, then re-hoist pos + this->encode_varint_raw_slow_(len); + pos = this->pos_; + } std::memcpy(pos, string, len); this->pos_ = pos + len; }