From 507c29add7da909118e8941059466b3ee7307df9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 7 Mar 2026 20:12:33 -1000 Subject: [PATCH] Inline encode_varint_raw fast path and add [[likely]] hints Apply the same inline fast-path / noinline slow-path split to ProtoWriteBuffer::encode_varint_raw that was done for ProtoSize::varint. For values < 128 (field tags, small field values, short lengths), the single-byte write is now inlined at each call site instead of going through a full function call. The multi-byte loop is outlined into encode_varint_raw_slow_(). Also add [[likely]] to both varint fast paths (ProtoSize::varint and encode_varint_raw) to hint branch prediction. --- esphome/components/api/proto.cpp | 10 ++++++++++ esphome/components/api/proto.h | 16 +++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 2f51c5c2a6b..fb229928e5a 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -10,6 +10,16 @@ static const char *const TAG = "api.proto"; uint32_t ProtoSize::varint_slow(uint32_t value) { return varint_wide(value); } +void ProtoWriteBuffer::encode_varint_raw_slow_(uint32_t value) { + do { + this->debug_check_bounds_(1); + *this->pos_++ = static_cast(value | 0x80); + value >>= 7; + } while (value > 0x7F); + this->debug_check_bounds_(1); + *this->pos_++ = static_cast(value); +} + #ifdef USE_API_VARINT64 optional ProtoVarInt::parse_wide(const uint8_t *buffer, uint32_t len, uint32_t *consumed, uint32_t result32) { diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index b6d68885b8f..adde0a8a85b 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -240,14 +240,13 @@ class ProtoWriteBuffer { 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) {} - void encode_varint_raw(uint32_t value) { - while (value > 0x7F) { + inline void ESPHOME_ALWAYS_INLINE encode_varint_raw(uint32_t value) { + if (value < 128) [[likely]] { this->debug_check_bounds_(1); - *this->pos_++ = static_cast(value | 0x80); - value >>= 7; + *this->pos_++ = static_cast(value); + return; } - this->debug_check_bounds_(1); - *this->pos_++ = static_cast(value); + this->encode_varint_raw_slow_(value); } void encode_varint_raw_64(uint64_t value) { while (value > 0x7F) { @@ -378,6 +377,9 @@ class ProtoWriteBuffer { std::vector *get_buffer() const { return buffer_; } protected: + // Slow path for encode_varint_raw values >= 128, outlined to keep fast path small + void encode_varint_raw_slow_(uint32_t value) __attribute__((noinline)); + #ifdef ESPHOME_DEBUG_API void debug_check_bounds_(size_t bytes, const char *caller = __builtin_FUNCTION()); void debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual); @@ -512,7 +514,7 @@ class ProtoSize { * @return The number of bytes needed to encode the value */ static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint(uint32_t value) { - if (value < 128) + if (value < 128) [[likely]] return 1; // Fast path: 7 bits, most common case if (__builtin_is_constant_evaluated()) return varint_wide(value);