mirror of
https://github.com/esphome/esphome.git
synced 2026-09-19 19:18:47 +00:00
cleanup
This commit is contained in:
@@ -1,795 +0,0 @@
|
||||
/**
|
||||
* Benchmark: ProtoWriteBuffer encoding performance
|
||||
*
|
||||
* Compares the old push_back()-based encoding against the new pre-sized
|
||||
* pointer-write approach introduced in PR #14018.
|
||||
*
|
||||
* Build (from repo root):
|
||||
* g++ -std=gnu++20 -O2 \
|
||||
* tests/benchmarks/proto_encode_benchmark.cpp \
|
||||
* -o tests/benchmarks/proto_encode_benchmark
|
||||
*
|
||||
* For ESP-like size-optimized builds (-Os):
|
||||
* g++ -std=gnu++20 -Os \
|
||||
* tests/benchmarks/proto_encode_benchmark.cpp \
|
||||
* -o tests/benchmarks/proto_encode_benchmark
|
||||
*
|
||||
* Run:
|
||||
* ./tests/benchmarks/proto_encode_benchmark
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// ============================================================================
|
||||
// Minimal stubs to avoid pulling in the full ESPHome framework
|
||||
// ============================================================================
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class StringRef {
|
||||
public:
|
||||
constexpr StringRef() : base_(""), len_(0) {}
|
||||
explicit StringRef(const char *s) : base_(s), len_(strlen(s)) {}
|
||||
constexpr StringRef(const char *s, size_t n) : base_(s), len_(n) {}
|
||||
explicit StringRef(const std::string &s) : base_(s.c_str()), len_(s.size()) {}
|
||||
|
||||
const char *c_str() const { return base_; }
|
||||
size_t size() const { return len_; }
|
||||
bool empty() const { return len_ == 0; }
|
||||
|
||||
private:
|
||||
const char *base_;
|
||||
size_t len_;
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
// ============================================================================
|
||||
// Old-style ProtoWriteBuffer (push_back based) - from dev branch
|
||||
// ============================================================================
|
||||
|
||||
class OldProtoWriteBuffer {
|
||||
public:
|
||||
explicit OldProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
|
||||
|
||||
void encode_varint_raw(uint32_t value) {
|
||||
while (value > 0x7F) {
|
||||
this->buffer_->push_back(static_cast<uint8_t>(value | 0x80));
|
||||
value >>= 7;
|
||||
}
|
||||
this->buffer_->push_back(static_cast<uint8_t>(value));
|
||||
}
|
||||
|
||||
void encode_varint_raw_64(uint64_t value) {
|
||||
while (value > 0x7F) {
|
||||
this->buffer_->push_back(static_cast<uint8_t>(value | 0x80));
|
||||
value >>= 7;
|
||||
}
|
||||
this->buffer_->push_back(static_cast<uint8_t>(value));
|
||||
}
|
||||
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
|
||||
|
||||
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);
|
||||
this->encode_varint_raw(len);
|
||||
size_t old_size = this->buffer_->size();
|
||||
this->buffer_->resize(old_size + len);
|
||||
std::memcpy(this->buffer_->data() + old_size, string, len);
|
||||
}
|
||||
|
||||
void encode_string(uint32_t field_id, const esphome::StringRef &ref, bool force = false) {
|
||||
this->encode_string(field_id, ref.c_str(), ref.size(), force);
|
||||
}
|
||||
|
||||
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(value);
|
||||
}
|
||||
|
||||
void encode_bool(uint32_t field_id, bool value, bool force = false) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->buffer_->push_back(value ? 0x01 : 0x00);
|
||||
}
|
||||
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 5);
|
||||
this->buffer_->push_back((value >> 0) & 0xFF);
|
||||
this->buffer_->push_back((value >> 8) & 0xFF);
|
||||
this->buffer_->push_back((value >> 16) & 0xFF);
|
||||
this->buffer_->push_back((value >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
void encode_float(uint32_t field_id, float value, bool force = false) {
|
||||
if (value == 0.0f && !force)
|
||||
return;
|
||||
union {
|
||||
float value;
|
||||
uint32_t raw;
|
||||
} val{};
|
||||
val.value = value;
|
||||
this->encode_fixed32(field_id, val.raw);
|
||||
}
|
||||
|
||||
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// New-style ProtoWriteBuffer (pointer-write based) - from this PR
|
||||
// ============================================================================
|
||||
|
||||
class NewProtoWriteBuffer {
|
||||
public:
|
||||
NewProtoWriteBuffer(std::vector<uint8_t> *buffer, size_t write_pos)
|
||||
: buffer_(buffer), pos_(buffer->data() + write_pos) {}
|
||||
|
||||
void encode_varint_raw(uint32_t value) {
|
||||
while (value > 0x7F) {
|
||||
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
}
|
||||
*this->pos_++ = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void encode_varint_raw_64(uint64_t value) {
|
||||
while (value > 0x7F) {
|
||||
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
}
|
||||
*this->pos_++ = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
|
||||
|
||||
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);
|
||||
this->encode_varint_raw(len);
|
||||
std::memcpy(this->pos_, string, len);
|
||||
this->pos_ += len;
|
||||
}
|
||||
|
||||
void encode_string(uint32_t field_id, const esphome::StringRef &ref, bool force = false) {
|
||||
this->encode_string(field_id, ref.c_str(), ref.size(), force);
|
||||
}
|
||||
|
||||
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(value);
|
||||
}
|
||||
|
||||
void encode_bool(uint32_t field_id, bool value, bool force = false) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
*this->pos_++ = value ? 0x01 : 0x00;
|
||||
}
|
||||
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 5);
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
std::memcpy(this->pos_, &value, 4);
|
||||
this->pos_ += 4;
|
||||
#else
|
||||
*this->pos_++ = (value >> 0) & 0xFF;
|
||||
*this->pos_++ = (value >> 8) & 0xFF;
|
||||
*this->pos_++ = (value >> 16) & 0xFF;
|
||||
*this->pos_++ = (value >> 24) & 0xFF;
|
||||
#endif
|
||||
}
|
||||
|
||||
void encode_float(uint32_t field_id, float value, bool force = false) {
|
||||
if (value == 0.0f && !force)
|
||||
return;
|
||||
union {
|
||||
float value;
|
||||
uint32_t raw;
|
||||
} val{};
|
||||
val.value = value;
|
||||
this->encode_fixed32(field_id, val.raw);
|
||||
}
|
||||
|
||||
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||
}
|
||||
|
||||
uint8_t *pos() const { return pos_; }
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> *buffer_;
|
||||
uint8_t *pos_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ProtoSize - calculate exact encoded size (shared by both approaches)
|
||||
// ============================================================================
|
||||
|
||||
class ProtoSize {
|
||||
public:
|
||||
static constexpr uint32_t varint(uint32_t value) {
|
||||
if (value < 128)
|
||||
return 1;
|
||||
if (value < 16384)
|
||||
return 2;
|
||||
if (value < 2097152)
|
||||
return 3;
|
||||
if (value < 268435456)
|
||||
return 4;
|
||||
return 5;
|
||||
}
|
||||
|
||||
static constexpr uint32_t field(uint32_t field_id, uint32_t type) { return varint((field_id << 3) | (type & 0x7)); }
|
||||
|
||||
static constexpr uint32_t calc_uint32(uint32_t field_id_size, uint32_t value) {
|
||||
return value ? field_id_size + varint(value) : 0;
|
||||
}
|
||||
|
||||
static constexpr uint32_t calc_bool(uint32_t field_id_size, bool value) { return value ? field_id_size + 1 : 0; }
|
||||
|
||||
static constexpr uint32_t calc_float(uint32_t field_id_size, float value) {
|
||||
return value != 0.0f ? field_id_size + 4 : 0;
|
||||
}
|
||||
|
||||
static constexpr uint32_t calc_fixed32(uint32_t field_id_size, uint32_t value) {
|
||||
return value ? field_id_size + 4 : 0;
|
||||
}
|
||||
|
||||
static constexpr uint32_t calc_length(uint32_t field_id_size, size_t len) {
|
||||
return len ? field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len) : 0;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark infrastructure
|
||||
// ============================================================================
|
||||
|
||||
struct BenchResult {
|
||||
const char *name;
|
||||
double ns_per_op;
|
||||
double ops_per_sec;
|
||||
size_t iterations;
|
||||
size_t bytes_per_op;
|
||||
};
|
||||
|
||||
// Prevent compiler from optimizing away the result
|
||||
template<typename T> __attribute__((noinline)) void do_not_optimize(T &value) {
|
||||
asm volatile("" : "+r,m"(value) : : "memory");
|
||||
}
|
||||
|
||||
__attribute__((noinline)) void clobber_memory() { asm volatile("" : : : "memory"); }
|
||||
|
||||
template<typename Func> BenchResult benchmark(const char *name, size_t bytes_per_op, Func func) {
|
||||
// Warmup
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
func();
|
||||
}
|
||||
|
||||
// Determine iteration count (target ~100ms)
|
||||
size_t iterations = 1000;
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
for (size_t i = 0; i < iterations; i++) {
|
||||
func();
|
||||
}
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
double elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
|
||||
double ns_per_op = elapsed_ns / iterations;
|
||||
|
||||
// Scale iterations to target ~200ms
|
||||
iterations = std::max<size_t>(10000, static_cast<size_t>(200'000'000.0 / ns_per_op));
|
||||
|
||||
// Actual benchmark run
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
for (size_t i = 0; i < iterations; i++) {
|
||||
func();
|
||||
clobber_memory();
|
||||
}
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
|
||||
ns_per_op = elapsed_ns / iterations;
|
||||
|
||||
return BenchResult{name, ns_per_op, 1'000'000'000.0 / ns_per_op, iterations, bytes_per_op};
|
||||
}
|
||||
|
||||
void print_results(const std::vector<BenchResult> &results) {
|
||||
printf("%-50s %12s %12s %12s %10s\n", "Benchmark", "ns/op", "ops/sec", "iters", "bytes/op");
|
||||
printf("%-50s %12s %12s %12s %10s\n", std::string(50, '-').c_str(), "--------", "--------", "--------", "--------");
|
||||
for (const auto &r : results) {
|
||||
printf("%-50s %12.1f %12.0f %12zu %10zu\n", r.name, r.ns_per_op, r.ops_per_sec, r.iterations, r.bytes_per_op);
|
||||
}
|
||||
}
|
||||
|
||||
void print_comparison(const char *label, const BenchResult &old_result, const BenchResult &new_result) {
|
||||
double speedup = old_result.ns_per_op / new_result.ns_per_op;
|
||||
printf(" %-46s %.1fx %s\n", label, speedup, speedup > 1.0 ? "faster" : "slower");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: Varint encoding
|
||||
// ============================================================================
|
||||
|
||||
static void bench_varint_old(std::vector<uint8_t> &buf) {
|
||||
buf.clear();
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
// Encode a mix of varint sizes (1-5 bytes)
|
||||
writer.encode_varint_raw(0x01); // 1 byte
|
||||
writer.encode_varint_raw(0x80); // 2 bytes
|
||||
writer.encode_varint_raw(0x4000); // 3 bytes
|
||||
writer.encode_varint_raw(0x200000); // 4 bytes
|
||||
writer.encode_varint_raw(0x10000000); // 5 bytes
|
||||
}
|
||||
|
||||
static void bench_varint_new(std::vector<uint8_t> &buf, size_t size) {
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
writer.encode_varint_raw(0x01);
|
||||
writer.encode_varint_raw(0x80);
|
||||
writer.encode_varint_raw(0x4000);
|
||||
writer.encode_varint_raw(0x200000);
|
||||
writer.encode_varint_raw(0x10000000);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: String encoding (simulates entity names, object_ids, etc.)
|
||||
// ============================================================================
|
||||
|
||||
static const char SHORT_STR[] = "sensor_1"; // 8 bytes
|
||||
static const char MEDIUM_STR[] = "living_room_temperature_sensor"; // 30 bytes
|
||||
static const char LONG_STR[] =
|
||||
"esphome_very_long_device_name_with_many_characters_for_testing_purposes_abcdef"; // 78 bytes
|
||||
|
||||
static void bench_strings_old(std::vector<uint8_t> &buf) {
|
||||
buf.clear();
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
writer.encode_string(1, SHORT_STR, strlen(SHORT_STR));
|
||||
writer.encode_string(2, MEDIUM_STR, strlen(MEDIUM_STR));
|
||||
writer.encode_string(3, LONG_STR, strlen(LONG_STR));
|
||||
}
|
||||
|
||||
static size_t calc_strings_size() {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, strlen(SHORT_STR));
|
||||
size += ProtoSize::calc_length(1, strlen(MEDIUM_STR));
|
||||
size += ProtoSize::calc_length(1, strlen(LONG_STR));
|
||||
return size;
|
||||
}
|
||||
|
||||
static void bench_strings_new(std::vector<uint8_t> &buf, size_t size) {
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
writer.encode_string(1, SHORT_STR, strlen(SHORT_STR));
|
||||
writer.encode_string(2, MEDIUM_STR, strlen(MEDIUM_STR));
|
||||
writer.encode_string(3, LONG_STR, strlen(LONG_STR));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: Fixed32 encoding (simulates key fields in state responses)
|
||||
// ============================================================================
|
||||
|
||||
static void bench_fixed32_old(std::vector<uint8_t> &buf) {
|
||||
buf.clear();
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
for (uint32_t i = 1; i <= 10; i++) {
|
||||
writer.encode_fixed32(i, 0xDEADBEEF);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t calc_fixed32_size() {
|
||||
uint32_t size = 0;
|
||||
for (uint32_t i = 1; i <= 10; i++) {
|
||||
size += ProtoSize::calc_fixed32(1, 0xDEADBEEF);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static void bench_fixed32_new(std::vector<uint8_t> &buf, size_t size) {
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
for (uint32_t i = 1; i <= 10; i++) {
|
||||
writer.encode_fixed32(i, 0xDEADBEEF);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: Simulate SensorStateResponse encoding
|
||||
// SensorStateResponse has: fixed32 key, float state, bool missing_state
|
||||
// This is the most frequent message type during normal operation.
|
||||
// ============================================================================
|
||||
|
||||
static void bench_sensor_state_old(std::vector<uint8_t> &buf) {
|
||||
buf.clear();
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
writer.encode_fixed32(1, 0x12345678); // key
|
||||
writer.encode_float(2, 23.5f); // state
|
||||
writer.encode_bool(3, false); // missing_state (default, skipped)
|
||||
}
|
||||
|
||||
static size_t calc_sensor_state_size() {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, 0x12345678);
|
||||
size += ProtoSize::calc_float(1, 23.5f);
|
||||
size += ProtoSize::calc_bool(1, false);
|
||||
return size;
|
||||
}
|
||||
|
||||
static void bench_sensor_state_new(std::vector<uint8_t> &buf, size_t size) {
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
writer.encode_fixed32(1, 0x12345678);
|
||||
writer.encode_float(2, 23.5f);
|
||||
writer.encode_bool(3, false);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: Simulate ListEntitiesSensorResponse encoding
|
||||
// This is a larger message sent during entity listing.
|
||||
// Fields: object_id, key, name, unique_id, icon, unit_of_measurement,
|
||||
// accuracy_decimals, force_update, device_class, state_class
|
||||
// ============================================================================
|
||||
|
||||
static const char OBJ_ID[] = "living_room_temp";
|
||||
static const char NAME[] = "Living Room Temperature";
|
||||
static const char UNIQUE_ID[] = "esp32_01-sensor-living_room_temp";
|
||||
static const char ICON[] = "mdi:thermometer";
|
||||
static const char UNIT[] = "\xc2\xb0"
|
||||
"C"; // UTF-8 degree C
|
||||
static const char DEVICE_CLASS[] = "temperature";
|
||||
|
||||
static void bench_list_entities_old(std::vector<uint8_t> &buf) {
|
||||
buf.clear();
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
writer.encode_string(1, OBJ_ID, strlen(OBJ_ID)); // object_id
|
||||
writer.encode_fixed32(2, 0xABCD1234); // key
|
||||
writer.encode_string(3, NAME, strlen(NAME)); // name
|
||||
writer.encode_string(4, UNIQUE_ID, strlen(UNIQUE_ID)); // unique_id
|
||||
writer.encode_string(5, ICON, strlen(ICON)); // icon
|
||||
writer.encode_string(6, UNIT, strlen(UNIT)); // unit_of_measurement
|
||||
writer.encode_uint32(7, 1); // accuracy_decimals
|
||||
writer.encode_bool(8, false); // force_update
|
||||
writer.encode_string(9, DEVICE_CLASS, strlen(DEVICE_CLASS)); // device_class
|
||||
writer.encode_uint32(10, 1); // state_class
|
||||
}
|
||||
|
||||
static size_t calc_list_entities_size() {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, strlen(OBJ_ID));
|
||||
size += ProtoSize::calc_fixed32(1, 0xABCD1234);
|
||||
size += ProtoSize::calc_length(1, strlen(NAME));
|
||||
size += ProtoSize::calc_length(1, strlen(UNIQUE_ID));
|
||||
size += ProtoSize::calc_length(1, strlen(ICON));
|
||||
size += ProtoSize::calc_length(1, strlen(UNIT));
|
||||
size += ProtoSize::calc_uint32(1, 1);
|
||||
size += ProtoSize::calc_bool(1, false);
|
||||
size += ProtoSize::calc_length(1, strlen(DEVICE_CLASS));
|
||||
size += ProtoSize::calc_uint32(1, 1);
|
||||
return size;
|
||||
}
|
||||
|
||||
static void bench_list_entities_new(std::vector<uint8_t> &buf, size_t size) {
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
writer.encode_string(1, OBJ_ID, strlen(OBJ_ID));
|
||||
writer.encode_fixed32(2, 0xABCD1234);
|
||||
writer.encode_string(3, NAME, strlen(NAME));
|
||||
writer.encode_string(4, UNIQUE_ID, strlen(UNIQUE_ID));
|
||||
writer.encode_string(5, ICON, strlen(ICON));
|
||||
writer.encode_string(6, UNIT, strlen(UNIT));
|
||||
writer.encode_uint32(7, 1);
|
||||
writer.encode_bool(8, false);
|
||||
writer.encode_string(9, DEVICE_CLASS, strlen(DEVICE_CLASS));
|
||||
writer.encode_uint32(10, 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: Simulate BLE advertisement batch encoding
|
||||
// BluetoothLERawAdvertisementsResponse with multiple advertisements.
|
||||
// Each advert has: uint64 address, sint32 rssi, uint32 address_type, bytes data
|
||||
// This is a high-frequency message that benefits most from optimization.
|
||||
// ============================================================================
|
||||
|
||||
static const uint8_t FAKE_BLE_DATA[31] = {0x02, 0x01, 0x06, 0x11, 0x07, 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00,
|
||||
0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x15, 0x12, 0x00, 0x00, 0x03,
|
||||
0x09, 0x54, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
static void bench_ble_batch_old(std::vector<uint8_t> &buf) {
|
||||
buf.clear();
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
// Simulate encoding 8 BLE advertisements
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// Each advertisement fields (flattened, no nested message for simplicity)
|
||||
writer.encode_uint32(1, static_cast<uint32_t>(0xAABBCCDD + i)); // address (lower 32)
|
||||
writer.encode_uint32(2, static_cast<uint32_t>(-70 + i)); // rssi
|
||||
writer.encode_uint32(3, 0); // address_type (public)
|
||||
writer.encode_bytes(4, FAKE_BLE_DATA, sizeof(FAKE_BLE_DATA)); // data
|
||||
}
|
||||
}
|
||||
|
||||
static size_t calc_ble_batch_size() {
|
||||
uint32_t size = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(0xAABBCCDD + i));
|
||||
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(-70 + i));
|
||||
size += ProtoSize::calc_uint32(1, 0);
|
||||
size += ProtoSize::calc_length(1, sizeof(FAKE_BLE_DATA));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static void bench_ble_batch_new(std::vector<uint8_t> &buf, size_t size) {
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
writer.encode_uint32(1, static_cast<uint32_t>(0xAABBCCDD + i));
|
||||
writer.encode_uint32(2, static_cast<uint32_t>(-70 + i));
|
||||
writer.encode_uint32(3, 0);
|
||||
writer.encode_bytes(4, FAKE_BLE_DATA, sizeof(FAKE_BLE_DATA));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: Simulate SubscribeLogsResponse encoding
|
||||
// This is a frequent message: level (enum/uint32) + message (bytes)
|
||||
// Message sizes vary from short to long log lines.
|
||||
// ============================================================================
|
||||
|
||||
static const char LOG_SHORT[] = "[sensor:042]: 'Temperature': Sending state 23.50 °C";
|
||||
static const char LOG_LONG[] = "[wifi:042]: Connecting to 'MyNetwork'... [wifi:042]: Connected! "
|
||||
"IP=192.168.1.100, SSID=MyNetwork, BSSID=AA:BB:CC:DD:EE:FF, Channel=6, RSSI=-42 dB";
|
||||
|
||||
static void bench_log_msg_old(std::vector<uint8_t> &buf) {
|
||||
buf.clear();
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
writer.encode_uint32(1, 3); // level = DEBUG
|
||||
writer.encode_bytes(3, reinterpret_cast<const uint8_t *>(LOG_SHORT), strlen(LOG_SHORT));
|
||||
}
|
||||
|
||||
static size_t calc_log_msg_size() {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, 3);
|
||||
size += ProtoSize::calc_length(1, strlen(LOG_SHORT));
|
||||
return size;
|
||||
}
|
||||
|
||||
static void bench_log_msg_new(std::vector<uint8_t> &buf, size_t size) {
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
writer.encode_uint32(1, 3);
|
||||
writer.encode_bytes(3, reinterpret_cast<const uint8_t *>(LOG_SHORT), strlen(LOG_SHORT));
|
||||
}
|
||||
|
||||
static void bench_log_long_old(std::vector<uint8_t> &buf) {
|
||||
buf.clear();
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
writer.encode_uint32(1, 3);
|
||||
writer.encode_bytes(3, reinterpret_cast<const uint8_t *>(LOG_LONG), strlen(LOG_LONG));
|
||||
}
|
||||
|
||||
static size_t calc_log_long_size() {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, 3);
|
||||
size += ProtoSize::calc_length(1, strlen(LOG_LONG));
|
||||
return size;
|
||||
}
|
||||
|
||||
static void bench_log_long_new(std::vector<uint8_t> &buf, size_t size) {
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
writer.encode_uint32(1, 3);
|
||||
writer.encode_bytes(3, reinterpret_cast<const uint8_t *>(LOG_LONG), strlen(LOG_LONG));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: Full encode cycle including calculate_size + resize + encode
|
||||
// This measures the realistic overhead of the pre-sizing approach.
|
||||
// ============================================================================
|
||||
|
||||
static void bench_full_cycle_sensor_old(std::vector<uint8_t> &buf) {
|
||||
// Old approach: just encode directly (vector grows as needed)
|
||||
buf.clear();
|
||||
buf.reserve(32); // Typical small reserve
|
||||
OldProtoWriteBuffer writer(&buf);
|
||||
writer.encode_fixed32(1, 0x12345678);
|
||||
writer.encode_float(2, 23.5f);
|
||||
writer.encode_bool(3, false);
|
||||
}
|
||||
|
||||
static void bench_full_cycle_sensor_new(std::vector<uint8_t> &buf) {
|
||||
// New approach: calculate size, resize, then encode
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, 0x12345678);
|
||||
size += ProtoSize::calc_float(1, 23.5f);
|
||||
size += ProtoSize::calc_bool(1, false);
|
||||
|
||||
buf.clear();
|
||||
buf.resize(size);
|
||||
NewProtoWriteBuffer writer(&buf, 0);
|
||||
writer.encode_fixed32(1, 0x12345678);
|
||||
writer.encode_float(2, 23.5f);
|
||||
writer.encode_bool(3, false);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Correctness verification
|
||||
// ============================================================================
|
||||
|
||||
static bool verify_encoding_match() {
|
||||
std::vector<uint8_t> old_buf, new_buf;
|
||||
bool all_pass = true;
|
||||
|
||||
auto check = [&](const char *name) {
|
||||
if (old_buf.size() != new_buf.size() || memcmp(old_buf.data(), new_buf.data(), old_buf.size()) != 0) {
|
||||
printf("FAIL: %s - output mismatch (old=%zu bytes, new=%zu bytes)\n", name, old_buf.size(), new_buf.size());
|
||||
all_pass = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Varint
|
||||
bench_varint_old(old_buf);
|
||||
bench_varint_new(new_buf, old_buf.size());
|
||||
check("varint");
|
||||
|
||||
// Strings
|
||||
bench_strings_old(old_buf);
|
||||
bench_strings_new(new_buf, calc_strings_size());
|
||||
check("strings");
|
||||
|
||||
// Fixed32
|
||||
bench_fixed32_old(old_buf);
|
||||
bench_fixed32_new(new_buf, calc_fixed32_size());
|
||||
check("fixed32");
|
||||
|
||||
// SensorStateResponse
|
||||
bench_sensor_state_old(old_buf);
|
||||
bench_sensor_state_new(new_buf, calc_sensor_state_size());
|
||||
check("sensor_state");
|
||||
|
||||
// ListEntitiesSensorResponse
|
||||
bench_list_entities_old(old_buf);
|
||||
bench_list_entities_new(new_buf, calc_list_entities_size());
|
||||
check("list_entities");
|
||||
|
||||
// BLE batch
|
||||
bench_ble_batch_old(old_buf);
|
||||
bench_ble_batch_new(new_buf, calc_ble_batch_size());
|
||||
check("ble_batch");
|
||||
|
||||
// Log message
|
||||
bench_log_msg_old(old_buf);
|
||||
bench_log_msg_new(new_buf, calc_log_msg_size());
|
||||
check("log_short");
|
||||
|
||||
// Long log message
|
||||
bench_log_long_old(old_buf);
|
||||
bench_log_long_new(new_buf, calc_log_long_size());
|
||||
check("log_long");
|
||||
|
||||
return all_pass;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main
|
||||
// ============================================================================
|
||||
|
||||
int main() {
|
||||
printf("=== ProtoWriteBuffer Encoding Benchmark ===\n");
|
||||
printf("Comparing push_back() vs pre-sized pointer writes\n\n");
|
||||
|
||||
// Verify correctness first
|
||||
printf("--- Correctness Verification ---\n");
|
||||
if (!verify_encoding_match()) {
|
||||
printf("CORRECTNESS CHECK FAILED - encoding output differs!\n");
|
||||
return 1;
|
||||
}
|
||||
printf("All encoding outputs match between old and new implementations.\n\n");
|
||||
|
||||
// Calculate sizes for pre-allocation
|
||||
size_t varint_size = 1 + 2 + 3 + 4 + 5; // 15 bytes
|
||||
size_t strings_size = calc_strings_size();
|
||||
size_t fixed32_size = calc_fixed32_size();
|
||||
size_t sensor_state_size = calc_sensor_state_size();
|
||||
size_t list_entities_size = calc_list_entities_size();
|
||||
size_t ble_batch_size = calc_ble_batch_size();
|
||||
size_t log_msg_size = calc_log_msg_size();
|
||||
size_t log_long_size = calc_log_long_size();
|
||||
|
||||
std::vector<uint8_t> buf;
|
||||
buf.reserve(1024); // Pre-allocate to avoid measuring allocation
|
||||
|
||||
std::vector<BenchResult> results;
|
||||
|
||||
// --- Varint encoding ---
|
||||
printf("--- Running Benchmarks ---\n\n");
|
||||
|
||||
results.push_back(benchmark("varint_mix (old/push_back)", varint_size, [&] { bench_varint_old(buf); }));
|
||||
results.push_back(benchmark("varint_mix (new/pointer)", varint_size, [&] { bench_varint_new(buf, varint_size); }));
|
||||
|
||||
// --- String encoding ---
|
||||
results.push_back(benchmark("strings_mix (old/push_back)", strings_size, [&] { bench_strings_old(buf); }));
|
||||
results.push_back(
|
||||
benchmark("strings_mix (new/pointer)", strings_size, [&] { bench_strings_new(buf, strings_size); }));
|
||||
|
||||
// --- Fixed32 encoding ---
|
||||
results.push_back(benchmark("fixed32_x10 (old/push_back)", fixed32_size, [&] { bench_fixed32_old(buf); }));
|
||||
results.push_back(
|
||||
benchmark("fixed32_x10 (new/pointer)", fixed32_size, [&] { bench_fixed32_new(buf, fixed32_size); }));
|
||||
|
||||
// --- SensorStateResponse ---
|
||||
results.push_back(benchmark("sensor_state (old/push_back)", sensor_state_size, [&] { bench_sensor_state_old(buf); }));
|
||||
results.push_back(benchmark("sensor_state (new/pointer)", sensor_state_size,
|
||||
[&] { bench_sensor_state_new(buf, sensor_state_size); }));
|
||||
|
||||
// --- ListEntitiesSensorResponse ---
|
||||
results.push_back(
|
||||
benchmark("list_entities (old/push_back)", list_entities_size, [&] { bench_list_entities_old(buf); }));
|
||||
results.push_back(benchmark("list_entities (new/pointer)", list_entities_size,
|
||||
[&] { bench_list_entities_new(buf, list_entities_size); }));
|
||||
|
||||
// --- BLE batch ---
|
||||
results.push_back(benchmark("ble_batch_x8 (old/push_back)", ble_batch_size, [&] { bench_ble_batch_old(buf); }));
|
||||
results.push_back(
|
||||
benchmark("ble_batch_x8 (new/pointer)", ble_batch_size, [&] { bench_ble_batch_new(buf, ble_batch_size); }));
|
||||
|
||||
// --- Log messages ---
|
||||
results.push_back(benchmark("log_short (old/push_back)", log_msg_size, [&] { bench_log_msg_old(buf); }));
|
||||
results.push_back(benchmark("log_short (new/pointer)", log_msg_size, [&] { bench_log_msg_new(buf, log_msg_size); }));
|
||||
|
||||
results.push_back(benchmark("log_long (old/push_back)", log_long_size, [&] { bench_log_long_old(buf); }));
|
||||
results.push_back(
|
||||
benchmark("log_long (new/pointer)", log_long_size, [&] { bench_log_long_new(buf, log_long_size); }));
|
||||
|
||||
// --- Full encode cycle (calculate_size + resize + encode) ---
|
||||
results.push_back(
|
||||
benchmark("full_cycle_sensor (old/push_back)", sensor_state_size, [&] { bench_full_cycle_sensor_old(buf); }));
|
||||
results.push_back(
|
||||
benchmark("full_cycle_sensor (new/pointer)", sensor_state_size, [&] { bench_full_cycle_sensor_new(buf); }));
|
||||
|
||||
// Print all results
|
||||
printf("\n--- Results ---\n\n");
|
||||
print_results(results);
|
||||
|
||||
// Print comparison summary
|
||||
printf("\n--- Speedup Summary (new vs old) ---\n\n");
|
||||
for (size_t i = 0; i + 1 < results.size(); i += 2) {
|
||||
print_comparison(results[i].name, results[i], results[i + 1]);
|
||||
}
|
||||
|
||||
printf("\n--- Encoded Sizes ---\n\n");
|
||||
printf(" varint_mix: %3zu bytes\n", varint_size);
|
||||
printf(" strings_mix: %3zu bytes\n", strings_size);
|
||||
printf(" fixed32_x10: %3zu bytes\n", fixed32_size);
|
||||
printf(" sensor_state: %3zu bytes\n", sensor_state_size);
|
||||
printf(" list_entities: %3zu bytes\n", list_entities_size);
|
||||
printf(" ble_batch_x8: %3zu bytes\n", ble_batch_size);
|
||||
printf(" log_short: %3zu bytes\n", log_msg_size);
|
||||
printf(" log_long: %3zu bytes\n", log_long_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,783 +0,0 @@
|
||||
/**
|
||||
* Benchmark: Virtual dispatch vs direct calls for protobuf message encoding
|
||||
*
|
||||
* Compares:
|
||||
* OLD: virtual dispatch for encode/calculate_size + ProtoSize accumulator object
|
||||
* NEW: direct template calls for encode/calculate_size + static ProtoSize methods
|
||||
*
|
||||
* Build (from repo root):
|
||||
* g++ -std=gnu++20 -O2 \
|
||||
* tests/benchmarks/proto_message_benchmark.cpp \
|
||||
* -o tests/benchmarks/proto_message_benchmark
|
||||
*
|
||||
* Run:
|
||||
* ./tests/benchmarks/proto_message_benchmark
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark infrastructure
|
||||
// ============================================================================
|
||||
|
||||
struct BenchResult {
|
||||
const char *name;
|
||||
double ns_per_op;
|
||||
double ops_per_sec;
|
||||
size_t iterations;
|
||||
};
|
||||
|
||||
template<typename T> __attribute__((noinline)) void do_not_optimize(T &value) {
|
||||
asm volatile("" : "+r,m"(value) : : "memory");
|
||||
}
|
||||
|
||||
__attribute__((noinline)) void clobber_memory() { asm volatile("" : : : "memory"); }
|
||||
|
||||
template<typename Func> BenchResult benchmark(const char *name, Func func) {
|
||||
// Warmup
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
func();
|
||||
}
|
||||
|
||||
// Determine iteration count (target ~100ms)
|
||||
size_t iterations = 1000;
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
for (size_t i = 0; i < iterations; i++) {
|
||||
func();
|
||||
}
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
double elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
|
||||
double ns_per_op = elapsed_ns / iterations;
|
||||
|
||||
// Scale iterations to target ~500ms for stability
|
||||
iterations = std::max<size_t>(100000, static_cast<size_t>(500'000'000.0 / ns_per_op));
|
||||
|
||||
// Actual benchmark run
|
||||
start = std::chrono::high_resolution_clock::now();
|
||||
for (size_t i = 0; i < iterations; i++) {
|
||||
func();
|
||||
clobber_memory();
|
||||
}
|
||||
end = std::chrono::high_resolution_clock::now();
|
||||
elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
|
||||
ns_per_op = elapsed_ns / iterations;
|
||||
|
||||
return BenchResult{name, ns_per_op, 1'000'000'000.0 / ns_per_op, iterations};
|
||||
}
|
||||
|
||||
void print_results(const std::vector<BenchResult> &results) {
|
||||
printf("%-55s %12s %15s %12s\n", "Benchmark", "ns/op", "ops/sec", "iters");
|
||||
printf("%-55s %12s %15s %12s\n", std::string(55, '-').c_str(), "--------", "--------", "--------");
|
||||
for (const auto &r : results) {
|
||||
printf("%-55s %12.1f %15.0f %12zu\n", r.name, r.ns_per_op, r.ops_per_sec, r.iterations);
|
||||
}
|
||||
}
|
||||
|
||||
void print_comparison(const char *label, const BenchResult &old_result, const BenchResult &new_result) {
|
||||
double speedup = old_result.ns_per_op / new_result.ns_per_op;
|
||||
const char *dir = speedup > 1.0 ? "faster" : "slower";
|
||||
printf(" %-51s %5.2fx %s\n", label, speedup > 1.0 ? speedup : 1.0 / speedup, dir);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Shared encoding helpers (same for both old and new)
|
||||
// ============================================================================
|
||||
|
||||
static constexpr uint32_t varint_size(uint32_t value) {
|
||||
if (value < 128)
|
||||
return 1;
|
||||
if (value < 16384)
|
||||
return 2;
|
||||
if (value < 2097152)
|
||||
return 3;
|
||||
if (value < 268435456)
|
||||
return 4;
|
||||
return 5;
|
||||
}
|
||||
|
||||
class WriteBuffer {
|
||||
public:
|
||||
WriteBuffer(std::vector<uint8_t> *buffer, size_t write_pos) : buffer_(buffer), pos_(buffer->data() + write_pos) {}
|
||||
|
||||
void encode_varint_raw(uint32_t value) {
|
||||
while (value > 0x7F) {
|
||||
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
}
|
||||
*this->pos_++ = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
|
||||
|
||||
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);
|
||||
this->encode_varint_raw(len);
|
||||
std::memcpy(this->pos_, string, len);
|
||||
this->pos_ += len;
|
||||
}
|
||||
|
||||
void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
this->encode_varint_raw(value);
|
||||
}
|
||||
|
||||
void encode_bool(uint32_t field_id, bool value, bool force = false) {
|
||||
if (!value && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0);
|
||||
*this->pos_++ = value ? 0x01 : 0x00;
|
||||
}
|
||||
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 5);
|
||||
std::memcpy(this->pos_, &value, 4);
|
||||
this->pos_ += 4;
|
||||
}
|
||||
|
||||
void encode_float(uint32_t field_id, float value, bool force = false) {
|
||||
if (value == 0.0f && !force)
|
||||
return;
|
||||
union {
|
||||
float value;
|
||||
uint32_t raw;
|
||||
} val{};
|
||||
val.value = value;
|
||||
this->encode_fixed32(field_id, val.raw);
|
||||
}
|
||||
|
||||
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||
}
|
||||
|
||||
// Nested message encoding (for old-style virtual dispatch)
|
||||
void encode_message_virtual(uint32_t field_id, uint32_t nested_size, const void *value,
|
||||
void (*encode_fn)(const void *, WriteBuffer &), bool force) {
|
||||
if (nested_size == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 2);
|
||||
this->encode_varint_raw(nested_size);
|
||||
encode_fn(value, *this);
|
||||
}
|
||||
|
||||
// Nested message encoding (for new-style direct calls)
|
||||
template<typename T> void encode_message(uint32_t field_id, const T &value, bool force = true) {
|
||||
uint32_t nested_size = value.calculate_size();
|
||||
if (nested_size == 0 && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 2);
|
||||
this->encode_varint_raw(nested_size);
|
||||
value.encode(*this);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *buffer_;
|
||||
uint8_t *pos_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// OLD approach: ProtoSize accumulator + virtual dispatch
|
||||
// ============================================================================
|
||||
|
||||
namespace old_style {
|
||||
|
||||
class ProtoSize {
|
||||
public:
|
||||
ProtoSize() = default;
|
||||
uint32_t get_size() const { return total_size_; }
|
||||
|
||||
void add_uint32(uint32_t field_id_size, uint32_t value) {
|
||||
if (value != 0)
|
||||
total_size_ += field_id_size + varint_size(value);
|
||||
}
|
||||
void add_bool(uint32_t field_id_size, bool value) {
|
||||
if (value)
|
||||
total_size_ += field_id_size + 1;
|
||||
}
|
||||
void add_float(uint32_t field_id_size, float value) {
|
||||
if (value != 0.0f)
|
||||
total_size_ += field_id_size + 4;
|
||||
}
|
||||
void add_fixed32(uint32_t field_id_size, uint32_t value) {
|
||||
if (value != 0)
|
||||
total_size_ += field_id_size + 4;
|
||||
}
|
||||
void add_length(uint32_t field_id_size, size_t len) {
|
||||
if (len != 0)
|
||||
total_size_ += field_id_size + varint_size(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
|
||||
}
|
||||
void add_message_field_force(uint32_t field_id_size, uint32_t nested_size) {
|
||||
total_size_ += field_id_size + varint_size(nested_size) + nested_size;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t total_size_ = 0;
|
||||
};
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual void encode(WriteBuffer &buffer) const = 0;
|
||||
virtual uint32_t calculate_size() const = 0;
|
||||
virtual ~ProtoMessage() = default;
|
||||
};
|
||||
|
||||
// Empty message (ping, disconnect, etc.)
|
||||
class EmptyMessage : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 1;
|
||||
void encode(WriteBuffer &buffer) const override {}
|
||||
uint32_t calculate_size() const override { return 0; }
|
||||
};
|
||||
|
||||
// SensorStateResponse: fixed32 key, float state, bool missing_state
|
||||
class SensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 25;
|
||||
uint32_t key{0x12345678};
|
||||
float state{23.5f};
|
||||
bool missing_state{false};
|
||||
|
||||
void encode(WriteBuffer &buffer) const override {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const override {
|
||||
ProtoSize size;
|
||||
size.add_fixed32(1, this->key);
|
||||
size.add_float(1, this->state);
|
||||
size.add_bool(1, this->missing_state);
|
||||
return size.get_size();
|
||||
}
|
||||
};
|
||||
|
||||
// ListEntitiesSensorResponse: multiple strings + numeric fields
|
||||
class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 16;
|
||||
std::string object_id{"living_room_temp"};
|
||||
uint32_t key{0xABCD1234};
|
||||
std::string name{"Living Room Temperature"};
|
||||
std::string unique_id{"esp32_01-sensor-living_room_temp"};
|
||||
std::string icon{"mdi:thermometer"};
|
||||
std::string unit_of_measurement{"\xc2\xb0"
|
||||
"C"};
|
||||
uint32_t accuracy_decimals{1};
|
||||
bool force_update{false};
|
||||
std::string device_class{"temperature"};
|
||||
uint32_t state_class{1};
|
||||
|
||||
void encode(WriteBuffer &buffer) const override {
|
||||
buffer.encode_string(1, this->object_id.data(), this->object_id.size());
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name.data(), this->name.size());
|
||||
buffer.encode_string(4, this->unique_id.data(), this->unique_id.size());
|
||||
buffer.encode_string(5, this->icon.data(), this->icon.size());
|
||||
buffer.encode_string(6, this->unit_of_measurement.data(), this->unit_of_measurement.size());
|
||||
buffer.encode_uint32(7, this->accuracy_decimals);
|
||||
buffer.encode_bool(8, this->force_update);
|
||||
buffer.encode_string(9, this->device_class.data(), this->device_class.size());
|
||||
buffer.encode_uint32(10, this->state_class);
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const override {
|
||||
ProtoSize size;
|
||||
size.add_length(1, this->object_id.size());
|
||||
size.add_fixed32(1, this->key);
|
||||
size.add_length(1, this->name.size());
|
||||
size.add_length(1, this->unique_id.size());
|
||||
size.add_length(1, this->icon.size());
|
||||
size.add_length(1, this->unit_of_measurement.size());
|
||||
size.add_uint32(1, this->accuracy_decimals);
|
||||
size.add_bool(1, this->force_update);
|
||||
size.add_length(1, this->device_class.size());
|
||||
size.add_uint32(1, this->state_class);
|
||||
return size.get_size();
|
||||
}
|
||||
};
|
||||
|
||||
// SubscribeLogsResponse: level + message bytes
|
||||
class SubscribeLogsResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 29;
|
||||
uint32_t level{3};
|
||||
std::string message{"[sensor:042]: 'Temperature': Sending state 23.50 C with 1 decimals of accuracy"};
|
||||
|
||||
void encode(WriteBuffer &buffer) const override {
|
||||
buffer.encode_uint32(1, this->level);
|
||||
buffer.encode_bytes(3, reinterpret_cast<const uint8_t *>(this->message.data()), this->message.size());
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const override {
|
||||
ProtoSize size;
|
||||
size.add_uint32(1, this->level);
|
||||
size.add_length(1, this->message.size());
|
||||
return size.get_size();
|
||||
}
|
||||
};
|
||||
|
||||
// Nested message: BluetoothGATTService with characteristics
|
||||
class BluetoothGATTCharacteristic : public ProtoMessage {
|
||||
public:
|
||||
uint32_t uuid1{0x2A19};
|
||||
uint32_t handle{3};
|
||||
uint32_t properties{2};
|
||||
|
||||
void encode(WriteBuffer &buffer) const override {
|
||||
buffer.encode_uint32(1, this->uuid1);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_uint32(3, this->properties);
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const override {
|
||||
ProtoSize size;
|
||||
size.add_uint32(1, this->uuid1);
|
||||
size.add_uint32(1, this->handle);
|
||||
size.add_uint32(1, this->properties);
|
||||
return size.get_size();
|
||||
}
|
||||
};
|
||||
|
||||
class BluetoothGATTService : public ProtoMessage {
|
||||
public:
|
||||
uint32_t uuid1{0x180F};
|
||||
uint32_t handle{1};
|
||||
std::vector<BluetoothGATTCharacteristic> characteristics;
|
||||
|
||||
BluetoothGATTService() { characteristics.resize(4); }
|
||||
|
||||
void encode(WriteBuffer &buffer) const override {
|
||||
buffer.encode_uint32(1, this->uuid1);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
for (const auto &ch : this->characteristics) {
|
||||
buffer.encode_message_virtual(
|
||||
3, ch.calculate_size(), &ch,
|
||||
[](const void *msg, WriteBuffer &buf) { static_cast<const BluetoothGATTCharacteristic *>(msg)->encode(buf); },
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const override {
|
||||
ProtoSize size;
|
||||
size.add_uint32(1, this->uuid1);
|
||||
size.add_uint32(1, this->handle);
|
||||
for (const auto &ch : this->characteristics) {
|
||||
size.add_message_field_force(1, ch.calculate_size());
|
||||
}
|
||||
return size.get_size();
|
||||
}
|
||||
};
|
||||
|
||||
// send_message simulation: virtual dispatch through base pointer
|
||||
__attribute__((noinline)) bool send_message(const ProtoMessage &msg, uint8_t msg_type, std::vector<uint8_t> &buf) {
|
||||
uint32_t size = msg.calculate_size();
|
||||
buf.resize(size);
|
||||
WriteBuffer writer(&buf, 0);
|
||||
msg.encode(writer);
|
||||
do_not_optimize(buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace old_style
|
||||
|
||||
// ============================================================================
|
||||
// NEW approach: static ProtoSize + direct template calls
|
||||
// ============================================================================
|
||||
|
||||
namespace new_style {
|
||||
|
||||
class ProtoSize {
|
||||
public:
|
||||
static constexpr uint32_t calc_uint32(uint32_t field_id_size, uint32_t value) {
|
||||
return value ? field_id_size + varint_size(value) : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_bool(uint32_t field_id_size, bool value) { return value ? field_id_size + 1 : 0; }
|
||||
static constexpr uint32_t calc_float(uint32_t field_id_size, float value) {
|
||||
return value != 0.0f ? field_id_size + 4 : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_fixed32(uint32_t field_id_size, uint32_t value) {
|
||||
return value ? field_id_size + 4 : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_length(uint32_t field_id_size, size_t len) {
|
||||
return len ? field_id_size + varint_size(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len) : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_message_force(uint32_t field_id_size, uint32_t nested_size) {
|
||||
return field_id_size + varint_size(nested_size) + nested_size;
|
||||
}
|
||||
};
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
// Non-virtual defaults — concrete types hide these
|
||||
void encode(WriteBuffer &buffer) const {}
|
||||
uint32_t calculate_size() const { return 0; }
|
||||
~ProtoMessage() = default;
|
||||
};
|
||||
|
||||
// Empty message
|
||||
class EmptyMessage : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 1;
|
||||
static constexpr uint32_t ESTIMATED_SIZE = 0;
|
||||
void encode(WriteBuffer &buffer) const {}
|
||||
uint32_t calculate_size() const { return 0; }
|
||||
};
|
||||
|
||||
// SensorStateResponse
|
||||
class SensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 25;
|
||||
static constexpr uint32_t ESTIMATED_SIZE = 10;
|
||||
uint32_t key{0x12345678};
|
||||
float state{23.5f};
|
||||
bool missing_state{false};
|
||||
|
||||
void encode(WriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += ProtoSize::calc_float(1, this->state);
|
||||
size += ProtoSize::calc_bool(1, this->missing_state);
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
// ListEntitiesSensorResponse
|
||||
class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 16;
|
||||
static constexpr uint32_t ESTIMATED_SIZE = 128;
|
||||
std::string object_id{"living_room_temp"};
|
||||
uint32_t key{0xABCD1234};
|
||||
std::string name{"Living Room Temperature"};
|
||||
std::string unique_id{"esp32_01-sensor-living_room_temp"};
|
||||
std::string icon{"mdi:thermometer"};
|
||||
std::string unit_of_measurement{"\xc2\xb0"
|
||||
"C"};
|
||||
uint32_t accuracy_decimals{1};
|
||||
bool force_update{false};
|
||||
std::string device_class{"temperature"};
|
||||
uint32_t state_class{1};
|
||||
|
||||
void encode(WriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id.data(), this->object_id.size());
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name.data(), this->name.size());
|
||||
buffer.encode_string(4, this->unique_id.data(), this->unique_id.size());
|
||||
buffer.encode_string(5, this->icon.data(), this->icon.size());
|
||||
buffer.encode_string(6, this->unit_of_measurement.data(), this->unit_of_measurement.size());
|
||||
buffer.encode_uint32(7, this->accuracy_decimals);
|
||||
buffer.encode_bool(8, this->force_update);
|
||||
buffer.encode_string(9, this->device_class.data(), this->device_class.size());
|
||||
buffer.encode_uint32(10, this->state_class);
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->object_id.size());
|
||||
size += ProtoSize::calc_fixed32(1, this->key);
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += ProtoSize::calc_length(1, this->unique_id.size());
|
||||
size += ProtoSize::calc_length(1, this->icon.size());
|
||||
size += ProtoSize::calc_length(1, this->unit_of_measurement.size());
|
||||
size += ProtoSize::calc_uint32(1, this->accuracy_decimals);
|
||||
size += ProtoSize::calc_bool(1, this->force_update);
|
||||
size += ProtoSize::calc_length(1, this->device_class.size());
|
||||
size += ProtoSize::calc_uint32(1, this->state_class);
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
// SubscribeLogsResponse
|
||||
class SubscribeLogsResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 29;
|
||||
static constexpr uint32_t ESTIMATED_SIZE = 80;
|
||||
uint32_t level{3};
|
||||
std::string message{"[sensor:042]: 'Temperature': Sending state 23.50 C with 1 decimals of accuracy"};
|
||||
|
||||
void encode(WriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->level);
|
||||
buffer.encode_bytes(3, reinterpret_cast<const uint8_t *>(this->message.data()), this->message.size());
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, this->level);
|
||||
size += ProtoSize::calc_length(1, this->message.size());
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
// Nested: BluetoothGATTCharacteristic
|
||||
class BluetoothGATTCharacteristic : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint32_t ESTIMATED_SIZE = 10;
|
||||
uint32_t uuid1{0x2A19};
|
||||
uint32_t handle{3};
|
||||
uint32_t properties{2};
|
||||
|
||||
void encode(WriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->uuid1);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_uint32(3, this->properties);
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, this->uuid1);
|
||||
size += ProtoSize::calc_uint32(1, this->handle);
|
||||
size += ProtoSize::calc_uint32(1, this->properties);
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
// Nested: BluetoothGATTService
|
||||
class BluetoothGATTService : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint32_t ESTIMATED_SIZE = 64;
|
||||
uint32_t uuid1{0x180F};
|
||||
uint32_t handle{1};
|
||||
std::vector<BluetoothGATTCharacteristic> characteristics;
|
||||
|
||||
BluetoothGATTService() { characteristics.resize(4); }
|
||||
|
||||
void encode(WriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->uuid1);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
for (const auto &ch : this->characteristics) {
|
||||
buffer.encode_message(3, ch, true);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, this->uuid1);
|
||||
size += ProtoSize::calc_uint32(1, this->handle);
|
||||
for (const auto &ch : this->characteristics) {
|
||||
size += ProtoSize::calc_message_force(1, ch.calculate_size());
|
||||
}
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
// Encode thunk for non-template core
|
||||
template<typename T> void encode_msg(const void *msg, WriteBuffer &buf) { static_cast<const T *>(msg)->encode(buf); }
|
||||
|
||||
static void encode_msg_noop(const void *, WriteBuffer &) {}
|
||||
|
||||
// send_message template: direct calls, no virtual dispatch
|
||||
template<typename T> __attribute__((noinline)) bool send_message(const T &msg, std::vector<uint8_t> &buf) {
|
||||
uint32_t size;
|
||||
void (*encode_fn)(const void *, WriteBuffer &);
|
||||
if constexpr (T::ESTIMATED_SIZE == 0) {
|
||||
size = 0;
|
||||
encode_fn = &encode_msg_noop;
|
||||
} else {
|
||||
size = msg.calculate_size();
|
||||
encode_fn = &encode_msg<T>;
|
||||
}
|
||||
buf.resize(size);
|
||||
WriteBuffer writer(&buf, 0);
|
||||
encode_fn(&msg, writer);
|
||||
do_not_optimize(buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace new_style
|
||||
|
||||
// ============================================================================
|
||||
// Correctness verification
|
||||
// ============================================================================
|
||||
|
||||
static bool verify_correctness() {
|
||||
std::vector<uint8_t> old_buf, new_buf;
|
||||
bool all_pass = true;
|
||||
|
||||
auto check = [&](const char *name) {
|
||||
if (old_buf.size() != new_buf.size() ||
|
||||
(old_buf.size() > 0 && memcmp(old_buf.data(), new_buf.data(), old_buf.size()) != 0)) {
|
||||
printf("FAIL: %s - output mismatch (old=%zu bytes, new=%zu bytes)\n", name, old_buf.size(), new_buf.size());
|
||||
all_pass = false;
|
||||
} else {
|
||||
printf(" OK: %s (%zu bytes)\n", name, old_buf.size());
|
||||
}
|
||||
};
|
||||
|
||||
// Empty
|
||||
{
|
||||
old_style::EmptyMessage old_msg;
|
||||
new_style::EmptyMessage new_msg;
|
||||
old_style::send_message(old_msg, old_msg.MESSAGE_TYPE, old_buf);
|
||||
new_style::send_message(new_msg, new_buf);
|
||||
check("EmptyMessage");
|
||||
}
|
||||
// SensorState
|
||||
{
|
||||
old_style::SensorStateResponse old_msg;
|
||||
new_style::SensorStateResponse new_msg;
|
||||
old_style::send_message(old_msg, old_msg.MESSAGE_TYPE, old_buf);
|
||||
new_style::send_message(new_msg, new_buf);
|
||||
check("SensorStateResponse");
|
||||
}
|
||||
// ListEntities
|
||||
{
|
||||
old_style::ListEntitiesSensorResponse old_msg;
|
||||
new_style::ListEntitiesSensorResponse new_msg;
|
||||
old_style::send_message(old_msg, old_msg.MESSAGE_TYPE, old_buf);
|
||||
new_style::send_message(new_msg, new_buf);
|
||||
check("ListEntitiesSensorResponse");
|
||||
}
|
||||
// Log
|
||||
{
|
||||
old_style::SubscribeLogsResponse old_msg;
|
||||
new_style::SubscribeLogsResponse new_msg;
|
||||
old_style::send_message(old_msg, old_msg.MESSAGE_TYPE, old_buf);
|
||||
new_style::send_message(new_msg, new_buf);
|
||||
check("SubscribeLogsResponse");
|
||||
}
|
||||
// Nested (GATT service)
|
||||
{
|
||||
old_style::BluetoothGATTService old_msg;
|
||||
new_style::BluetoothGATTService new_msg;
|
||||
old_style::send_message(old_msg, 7, old_buf);
|
||||
new_style::send_message(new_msg, new_buf);
|
||||
check("BluetoothGATTService (nested)");
|
||||
}
|
||||
|
||||
return all_pass;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Benchmark: calculate_size only
|
||||
// ============================================================================
|
||||
|
||||
template<typename T> __attribute__((noinline)) uint32_t bench_calc_size_virtual(const T &msg) {
|
||||
// Force virtual dispatch by going through base pointer
|
||||
const old_style::ProtoMessage *base = &msg;
|
||||
uint32_t s = base->calculate_size();
|
||||
do_not_optimize(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
template<typename T> __attribute__((noinline)) uint32_t bench_calc_size_direct(const T &msg) {
|
||||
uint32_t s = msg.calculate_size();
|
||||
do_not_optimize(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main
|
||||
// ============================================================================
|
||||
|
||||
int main() {
|
||||
printf("=== Proto Message Encoding Benchmark ===\n");
|
||||
printf("Comparing virtual dispatch + accumulator ProtoSize vs direct calls + static ProtoSize\n\n");
|
||||
|
||||
// Verify correctness
|
||||
printf("--- Correctness Verification ---\n");
|
||||
if (!verify_correctness()) {
|
||||
printf("\nCORRECTNESS CHECK FAILED!\n");
|
||||
return 1;
|
||||
}
|
||||
printf("All outputs match.\n\n");
|
||||
|
||||
std::vector<uint8_t> buf;
|
||||
buf.reserve(1024);
|
||||
|
||||
std::vector<BenchResult> results;
|
||||
|
||||
// ---- calculate_size benchmarks ----
|
||||
printf("--- Running calculate_size Benchmarks ---\n\n");
|
||||
|
||||
{
|
||||
old_style::SensorStateResponse old_msg;
|
||||
new_style::SensorStateResponse new_msg;
|
||||
results.push_back(benchmark("calc_size: SensorState (virtual)", [&] { bench_calc_size_virtual(old_msg); }));
|
||||
results.push_back(benchmark("calc_size: SensorState (direct+static)", [&] { bench_calc_size_direct(new_msg); }));
|
||||
}
|
||||
{
|
||||
old_style::ListEntitiesSensorResponse old_msg;
|
||||
new_style::ListEntitiesSensorResponse new_msg;
|
||||
results.push_back(benchmark("calc_size: ListEntities (virtual)", [&] { bench_calc_size_virtual(old_msg); }));
|
||||
results.push_back(benchmark("calc_size: ListEntities (direct+static)", [&] { bench_calc_size_direct(new_msg); }));
|
||||
}
|
||||
{
|
||||
old_style::SubscribeLogsResponse old_msg;
|
||||
new_style::SubscribeLogsResponse new_msg;
|
||||
results.push_back(benchmark("calc_size: LogResponse (virtual)", [&] { bench_calc_size_virtual(old_msg); }));
|
||||
results.push_back(benchmark("calc_size: LogResponse (direct+static)", [&] { bench_calc_size_direct(new_msg); }));
|
||||
}
|
||||
{
|
||||
old_style::BluetoothGATTService old_msg;
|
||||
new_style::BluetoothGATTService new_msg;
|
||||
results.push_back(benchmark("calc_size: GATTService/nested (virtual)", [&] { bench_calc_size_virtual(old_msg); }));
|
||||
results.push_back(
|
||||
benchmark("calc_size: GATTService/nested (direct+static)", [&] { bench_calc_size_direct(new_msg); }));
|
||||
}
|
||||
|
||||
// ---- Full send_message benchmarks ----
|
||||
printf("--- Running send_message Benchmarks ---\n\n");
|
||||
|
||||
{
|
||||
old_style::EmptyMessage old_msg;
|
||||
new_style::EmptyMessage new_msg;
|
||||
results.push_back(benchmark("send: EmptyMessage (virtual)", [&] { old_style::send_message(old_msg, 1, buf); }));
|
||||
results.push_back(benchmark("send: EmptyMessage (direct+static)", [&] { new_style::send_message(new_msg, buf); }));
|
||||
}
|
||||
{
|
||||
old_style::SensorStateResponse old_msg;
|
||||
new_style::SensorStateResponse new_msg;
|
||||
results.push_back(benchmark("send: SensorState (virtual)", [&] { old_style::send_message(old_msg, 25, buf); }));
|
||||
results.push_back(benchmark("send: SensorState (direct+static)", [&] { new_style::send_message(new_msg, buf); }));
|
||||
}
|
||||
{
|
||||
old_style::ListEntitiesSensorResponse old_msg;
|
||||
new_style::ListEntitiesSensorResponse new_msg;
|
||||
results.push_back(benchmark("send: ListEntities (virtual)", [&] { old_style::send_message(old_msg, 16, buf); }));
|
||||
results.push_back(benchmark("send: ListEntities (direct+static)", [&] { new_style::send_message(new_msg, buf); }));
|
||||
}
|
||||
{
|
||||
old_style::SubscribeLogsResponse old_msg;
|
||||
new_style::SubscribeLogsResponse new_msg;
|
||||
results.push_back(benchmark("send: LogResponse (virtual)", [&] { old_style::send_message(old_msg, 29, buf); }));
|
||||
results.push_back(benchmark("send: LogResponse (direct+static)", [&] { new_style::send_message(new_msg, buf); }));
|
||||
}
|
||||
{
|
||||
old_style::BluetoothGATTService old_msg;
|
||||
new_style::BluetoothGATTService new_msg;
|
||||
results.push_back(
|
||||
benchmark("send: GATTService/nested (virtual)", [&] { old_style::send_message(old_msg, 7, buf); }));
|
||||
results.push_back(
|
||||
benchmark("send: GATTService/nested (direct+static)", [&] { new_style::send_message(new_msg, buf); }));
|
||||
}
|
||||
|
||||
// Print all results
|
||||
printf("\n--- Results ---\n\n");
|
||||
print_results(results);
|
||||
|
||||
// Print comparison summary
|
||||
printf("\n--- Speedup Summary (new vs old) ---\n\n");
|
||||
for (size_t i = 0; i + 1 < results.size(); i += 2) {
|
||||
print_comparison(results[i].name, results[i], results[i + 1]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user