From 52897fd0673e0005bf7d5377a6c4b1964b788228 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Mar 2026 17:27:40 -1000 Subject: [PATCH] [api] Add 2-byte inline fast path for sint32 varint encode/size Add encode_varint_raw_short() and ProtoSize::varint_short() that inline both the 1-byte and 2-byte varint paths, falling back to the noinline slow path for 3+ bytes. Use these for sint32 fields (zigzag encoding), where values like RSSI (-100 to 0) produce zigzag values that are 1-2 bytes. This avoids a function call for the common case without bloating the generic encode_varint_raw fast path. --- esphome/components/api/api_pb2.cpp | 2 +- esphome/components/api/proto.h | 31 +++++++++++++++++++++++++++-- script/api_protobuf/api_protobuf.py | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index ae2cd2bae8d..9515b9c3b6f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -2252,7 +2252,7 @@ void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer &buffer) const { buffer.write_raw_byte(8); buffer.encode_varint_raw_64(this->address); buffer.write_raw_byte(16); - buffer.encode_varint_raw(encode_zigzag32(this->rssi)); + buffer.encode_varint_raw_short(encode_zigzag32(this->rssi)); buffer.encode_uint32(3, this->address_type); buffer.write_raw_byte(34); buffer.encode_varint_raw(this->data_len); diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index b629018a919..efec7af7071 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -207,6 +207,22 @@ class ProtoWriteBuffer { } this->encode_varint_raw_slow_(value); } + /// Encode a varint that is expected to be 1-2 bytes (e.g. zigzag RSSI, small lengths). + /// Inlines both the 1-byte and 2-byte paths; falls back to slow path for 3+ bytes. + inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_short(uint32_t value) { + if (value < 128) [[likely]] { + this->debug_check_bounds_(1); + *this->pos_++ = static_cast(value); + return; + } + if (value < 16384) [[likely]] { + this->debug_check_bounds_(2); + *this->pos_++ = static_cast(value | 0x80); + *this->pos_++ = static_cast(value >> 7); + return; + } + this->encode_varint_raw_slow_(value); + } void encode_varint_raw_64(uint64_t value) { while (value > 0x7F) { this->debug_check_bounds_(1); @@ -531,6 +547,17 @@ class ProtoSize { return varint_wide(value); return varint_slow(value); } + /// Size of a varint expected to be 1-2 bytes (e.g. zigzag RSSI, small lengths). + /// Inlines both checks; falls back to slow path for 3+ bytes. + static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint_short(uint32_t value) { + if (value < VARINT_THRESHOLD_1_BYTE) [[likely]] + return 1; + if (value < VARINT_THRESHOLD_2_BYTE) [[likely]] + return 2; + if (__builtin_is_constant_evaluated()) + return varint_wide(value); + return varint_slow(value); + } private: // Slow path for varint >= 128, outlined to keep fast path small @@ -645,10 +672,10 @@ class ProtoSize { return value ? field_id_size + 4 : 0; } static constexpr uint32_t calc_sint32(uint32_t field_id_size, int32_t value) { - return value ? field_id_size + varint(encode_zigzag32(value)) : 0; + return value ? field_id_size + varint_short(encode_zigzag32(value)) : 0; } static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_sint32_force(uint32_t field_id_size, int32_t value) { - return field_id_size + varint(encode_zigzag32(value)); + return field_id_size + varint_short(encode_zigzag32(value)); } static constexpr uint32_t calc_int64(uint32_t field_id_size, int64_t value) { return value ? field_id_size + varint(value) : 0; diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index f2a11141af0..811e7d12d75 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -229,7 +229,7 @@ class TypeInfo(ABC): RAW_ENCODE_MAP: dict[str, str] = { "encode_uint32": "buffer.encode_varint_raw({value});", "encode_uint64": "buffer.encode_varint_raw_64({value});", - "encode_sint32": "buffer.encode_varint_raw(encode_zigzag32({value}));", + "encode_sint32": "buffer.encode_varint_raw_short(encode_zigzag32({value}));", "encode_sint64": "buffer.encode_varint_raw_64(encode_zigzag64({value}));", "encode_int64": "buffer.encode_varint_raw_64(static_cast({value}));", "encode_bool": "buffer.write_raw_byte({value} ? 0x01 : 0x00);",