1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 23:51:47 +00:00

Compare commits

...

14 Commits

Author SHA1 Message Date
J. Nick Koston
ed4c2cc8be [api] Optimize protobuf varint encoding for flash savings 2025-10-28 22:13:07 -05:00
J. Nick Koston
f96c4ad721 likely 2025-10-28 21:59:43 -05:00
J. Nick Koston
5b5388b3ff likely 2025-10-28 21:58:36 -05:00
J. Nick Koston
1e3195676b likely 2025-10-28 21:56:10 -05:00
J. Nick Koston
ea14f374e7 adj 2025-10-28 21:46:22 -05:00
J. Nick Koston
81fdc14d7f tweak 2025-10-28 21:43:03 -05:00
J. Nick Koston
c96b8aaf2d tweak 2025-10-28 21:41:37 -05:00
J. Nick Koston
82c0f889ed tweak 2025-10-28 21:38:17 -05:00
J. Nick Koston
61bb4ef4f0 tweak 2025-10-28 21:37:19 -05:00
J. Nick Koston
aa98ccf3fd tweak 2025-10-28 21:31:26 -05:00
J. Nick Koston
b754a3c1b3 Avoid vector growth protobuf 2025-10-28 21:22:43 -05:00
J. Nick Koston
d781fc7210 Avoid vector growth protobuf 2025-10-28 21:22:34 -05:00
J. Nick Koston
aed34e78c8 Avoid vector growth protobuf 2025-10-28 21:22:19 -05:00
J. Nick Koston
36ffe21bfd merge 2025-10-28 21:21:48 -05:00
2 changed files with 98 additions and 22 deletions

View File

@@ -3,10 +3,12 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/macros.h"
#include "esphome/core/string_ref.h"
#include <cassert>
#include <cstring>
#include <type_traits>
#include <vector>
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
@@ -159,22 +161,6 @@ class ProtoVarInt {
}
}
}
void encode(std::vector<uint8_t> &out) {
uint64_t val = this->value_;
if (val <= 0x7F) {
out.push_back(val);
return;
}
while (val) {
uint8_t temp = val & 0x7F;
val >>= 7;
if (val) {
out.push_back(temp | 0x80);
} else {
out.push_back(temp);
}
}
}
protected:
uint64_t value_;
@@ -233,8 +219,87 @@ class ProtoWriteBuffer {
public:
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
void write(uint8_t value) { this->buffer_->push_back(value); }
void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
// Single implementation that all overloads delegate to
// Mark noinline to prevent code bloat from inlining into every caller
__attribute__((noinline)) void encode_varint(uint64_t value) {
auto buffer = this->buffer_;
size_t start = buffer->size();
// Fast paths for common cases (1-3 bytes)
if (ESPHOME_LIKELY(value < (1ULL << 7))) {
// 1 byte - very common for field IDs and small lengths
buffer->resize(start + 1);
buffer->data()[start] = static_cast<uint8_t>(value);
return;
}
uint8_t *p;
if (ESPHOME_LIKELY(value < (1ULL << 14))) {
// 2 bytes - common for medium field IDs and lengths
buffer->resize(start + 2);
p = buffer->data() + start;
p[0] = (value & 0x7F) | 0x80;
p[1] = (value >> 7) & 0x7F;
return;
}
if (value < (1ULL << 21)) {
// 3 bytes - rare
buffer->resize(start + 3);
p = buffer->data() + start;
p[0] = (value & 0x7F) | 0x80;
p[1] = ((value >> 7) & 0x7F) | 0x80;
p[2] = (value >> 14) & 0x7F;
return;
}
// Rare case: 4-10 byte values - calculate size from bit position
// Value is guaranteed >= (1ULL << 21), so CLZ is safe (non-zero)
uint32_t size;
#if defined(__GNUC__) || defined(__clang__)
// Use compiler intrinsic for efficient bit position lookup
size = (64 - __builtin_clzll(value) + 6) / 7;
#else
// Fallback for compilers without __builtin_clzll
if (value < (1ULL << 28)) {
size = 4;
} else if (value < (1ULL << 35)) {
size = 5;
} else if (value < (1ULL << 42)) {
size = 6;
} else if (value < (1ULL << 49)) {
size = 7;
} else if (value < (1ULL << 56)) {
size = 8;
} else if (value < (1ULL << 63)) {
size = 9;
} else {
size = 10;
}
#endif
buffer->resize(start + size);
p = buffer->data() + start;
size_t bytes = 0;
while (value) {
uint8_t temp = value & 0x7F;
value >>= 7;
p[bytes++] = value ? temp | 0x80 : temp;
}
}
// Common case: uint32_t values (field IDs, lengths, most integers)
void encode_varint(uint32_t value) { this->encode_varint(static_cast<uint64_t>(value)); }
// size_t overload (only enabled if size_t is distinct from uint32_t and uint64_t)
template<typename T>
void encode_varint(T value) requires(std::is_same_v<T, size_t> && !std::is_same_v<size_t, uint32_t> &&
!std::is_same_v<size_t, uint64_t>) {
this->encode_varint(static_cast<uint64_t>(value));
}
// Rare case: ProtoVarInt wrapper
void encode_varint(ProtoVarInt value) { this->encode_varint(value.as_uint64()); }
/**
* Encode a field key (tag/wire type combination).
*
@@ -249,14 +314,14 @@ class ProtoWriteBuffer {
*/
void encode_field_raw(uint32_t field_id, uint32_t type) {
uint32_t val = (field_id << 3) | (type & WIRE_TYPE_MASK);
this->encode_varint_raw(val);
this->encode_varint(val);
}
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
if (len == 0 && !force)
return;
this->encode_field_raw(field_id, 2); // type 2: Length-delimited string
this->encode_varint_raw(len);
this->encode_varint(len);
// Using resize + memcpy instead of insert provides significant performance improvement:
// ~10-11x faster for 16-32 byte strings, ~3x faster for 64-byte strings
@@ -278,13 +343,13 @@ class ProtoWriteBuffer {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 0); // type 0: Varint - uint32
this->encode_varint_raw(value);
this->encode_varint(value);
}
void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
if (value == 0 && !force)
return;
this->encode_field_raw(field_id, 0); // type 0: Varint - uint64
this->encode_varint_raw(ProtoVarInt(value));
this->encode_varint(value);
}
void encode_bool(uint32_t field_id, bool value, bool force = false) {
if (!value && !force)

View File

@@ -3,6 +3,17 @@
// Helper macro to define a version code, whose value can be compared against other version codes.
#define VERSION_CODE(major, minor, patch) ((major) << 16 | (minor) << 8 | (patch))
// Branch prediction hints for performance-critical paths
#if defined(__GNUC__) || defined(__clang__)
// GCC and Clang: use __builtin_expect for better optimization
#define ESPHOME_LIKELY(x) __builtin_expect(!!(x), 1)
#define ESPHOME_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
// Other C++20 compilers: use standard attributes
#define ESPHOME_LIKELY(x) (x) [[likely]]
#define ESPHOME_UNLIKELY(x) (x) [[unlikely]]
#endif
#ifdef USE_ARDUINO
#include <Arduino.h>
#endif