mirror of
https://github.com/esphome/esphome.git
synced 2025-09-02 11:22:24 +01:00
[api] Optimize varint encoding performance for Bluetooth proxy efficiency
This commit is contained in:
@@ -235,8 +235,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
|||||||
|
|
||||||
for (const auto &packet : packets) {
|
for (const auto &packet : packets) {
|
||||||
// Calculate varint sizes for header layout
|
// Calculate varint sizes for header layout
|
||||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
|
uint8_t size_varint_len = api::ProtoSize::varint(packet.payload_size);
|
||||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
|
uint8_t type_varint_len = api::ProtoSize::varint(packet.message_type);
|
||||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||||
|
|
||||||
// Calculate where to start writing the header
|
// Calculate where to start writing the header
|
||||||
@@ -271,9 +271,8 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
|||||||
buf_start[header_offset] = 0x00; // indicator
|
buf_start[header_offset] = 0x00; // indicator
|
||||||
|
|
||||||
// Encode varints directly into buffer
|
// Encode varints directly into buffer
|
||||||
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
encode_varint_unchecked(buf_start + header_offset + 1, packet.payload_size);
|
||||||
ProtoVarInt(packet.message_type)
|
encode_varint_unchecked(buf_start + header_offset + 1 + size_varint_len, packet.message_type);
|
||||||
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
|
||||||
|
|
||||||
// Add iovec for this packet (header + payload)
|
// Add iovec for this packet (header + payload)
|
||||||
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
||||||
|
@@ -124,34 +124,6 @@ class ProtoVarInt {
|
|||||||
// with ZigZag encoding
|
// with ZigZag encoding
|
||||||
return decode_zigzag64(this->value_);
|
return decode_zigzag64(this->value_);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
|
||||||
*
|
|
||||||
* @param buffer The pre-allocated buffer to write the encoded varint to
|
|
||||||
* @param len The size of the buffer in bytes
|
|
||||||
*
|
|
||||||
* @note The caller is responsible for ensuring the buffer is large enough
|
|
||||||
* to hold the encoded value. Use ProtoSize::varint() to calculate
|
|
||||||
* the exact size needed before calling this method.
|
|
||||||
* @note No bounds checking is performed for performance reasons.
|
|
||||||
*/
|
|
||||||
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
|
|
||||||
uint64_t val = this->value_;
|
|
||||||
if (val <= 0x7F) {
|
|
||||||
buffer[0] = val;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
size_t i = 0;
|
|
||||||
while (val && i < len) {
|
|
||||||
uint8_t temp = val & 0x7F;
|
|
||||||
val >>= 7;
|
|
||||||
if (val) {
|
|
||||||
buffer[i++] = temp | 0x80;
|
|
||||||
} else {
|
|
||||||
buffer[i++] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void encode(std::vector<uint8_t> &out) {
|
void encode(std::vector<uint8_t> &out) {
|
||||||
uint64_t val = this->value_;
|
uint64_t val = this->value_;
|
||||||
if (val <= 0x7F) {
|
if (val <= 0x7F) {
|
||||||
@@ -330,6 +302,28 @@ class ProtoWriteBuffer {
|
|||||||
std::vector<uint8_t> *buffer_;
|
std::vector<uint8_t> *buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encode a uint16_t value as a varint directly to a buffer without bounds checking
|
||||||
|
*
|
||||||
|
* @param buffer The pre-allocated buffer to write the encoded varint to
|
||||||
|
* @param value The uint16_t value to encode (0-65535)
|
||||||
|
*
|
||||||
|
* @note The caller is responsible for ensuring the buffer is large enough (max 3 bytes for uint16_t)
|
||||||
|
* @note No bounds checking is performed for performance reasons
|
||||||
|
*/
|
||||||
|
inline void encode_varint_unchecked(uint8_t *buffer, uint16_t value) {
|
||||||
|
if (value < 128) {
|
||||||
|
buffer[0] = value;
|
||||||
|
} else if (value < 16384) {
|
||||||
|
buffer[0] = (value & 0x7F) | 0x80;
|
||||||
|
buffer[1] = value >> 7;
|
||||||
|
} else {
|
||||||
|
buffer[0] = (value & 0x7F) | 0x80;
|
||||||
|
buffer[1] = ((value >> 7) & 0x7F) | 0x80;
|
||||||
|
buffer[2] = value >> 14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class ProtoSize;
|
class ProtoSize;
|
||||||
|
|
||||||
@@ -386,6 +380,33 @@ class ProtoSize {
|
|||||||
|
|
||||||
uint32_t get_size() const { return total_size_; }
|
uint32_t get_size() const { return total_size_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode a uint8_t value as a varint
|
||||||
|
*
|
||||||
|
* @param value The uint8_t value to calculate size for
|
||||||
|
* @return The number of bytes needed to encode the value (1 or 2)
|
||||||
|
*/
|
||||||
|
static constexpr uint8_t varint(uint8_t value) {
|
||||||
|
// For uint8_t (0-255), we need at most 2 bytes
|
||||||
|
return (value < 128) ? 1 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the size in bytes needed to encode a uint16_t value as a varint
|
||||||
|
*
|
||||||
|
* @param value The uint16_t value to calculate size for
|
||||||
|
* @return The number of bytes needed to encode the value (1-3)
|
||||||
|
*/
|
||||||
|
static constexpr uint8_t varint(uint16_t value) {
|
||||||
|
// For uint16_t (0-65535), we need at most 3 bytes
|
||||||
|
if (value < 128)
|
||||||
|
return 1; // 7 bits
|
||||||
|
else if (value < 16384)
|
||||||
|
return 2; // 14 bits
|
||||||
|
else
|
||||||
|
return 3; // 15-16 bits
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
||||||
*
|
*
|
||||||
@@ -395,11 +416,9 @@ class ProtoSize {
|
|||||||
static constexpr uint32_t varint(uint32_t value) {
|
static constexpr uint32_t varint(uint32_t value) {
|
||||||
// Optimized varint size calculation using leading zeros
|
// Optimized varint size calculation using leading zeros
|
||||||
// Each 7 bits requires one byte in the varint encoding
|
// Each 7 bits requires one byte in the varint encoding
|
||||||
if (value < 128)
|
if (value < 128) {
|
||||||
return 1; // 7 bits, common case for small values
|
return 1; // 7 bits, common case for small values
|
||||||
|
} else if (value < 16384) {
|
||||||
// For larger values, count bytes needed based on the position of the highest bit set
|
|
||||||
if (value < 16384) {
|
|
||||||
return 2; // 14 bits
|
return 2; // 14 bits
|
||||||
} else if (value < 2097152) {
|
} else if (value < 2097152) {
|
||||||
return 3; // 21 bits
|
return 3; // 21 bits
|
||||||
@@ -773,7 +792,7 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa
|
|||||||
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
|
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
|
||||||
|
|
||||||
// Write the length varint directly
|
// Write the length varint directly
|
||||||
ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
|
encode_varint_unchecked(this->buffer_->data() + begin, static_cast<uint16_t>(msg_length_bytes));
|
||||||
|
|
||||||
// Now encode the message content - it will append to the buffer
|
// Now encode the message content - it will append to the buffer
|
||||||
value.encode(*this);
|
value.encode(*this);
|
||||||
|
Reference in New Issue
Block a user