mirror of
https://github.com/esphome/esphome.git
synced 2026-10-01 00:40:21 +00:00
[api] Move proxy message benchmarks into bench_proto_proxy.cpp
Splitting these out from bench_proto_encode.cpp and bench_proto_decode.cpp moves them to the end of the linker's static-init order. CodSpeed's callgrind runner has been segfaulting immediately after measuring the last existing decode benchmark (Decode_SwitchCommandRequest), and isolating the new code into its own translation unit lets us see whether the crash is triggered by one of the new benchmarks or by something about the new USE_*_PROXY/USE_INFRARED/USE_RADIO_FREQUENCY defines changing how api_pb2.cpp compiles.
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
// Encode/decode microbenchmarks for proxy message families that carry
|
||||
// high-volume traffic (Z-Wave, IR/RF, serial). Mirrors the existing
|
||||
// BluetoothLERawAdvertisementsResponse benchmarks in bench_proto_encode.cpp.
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/api/api_buffer.h"
|
||||
|
||||
namespace esphome::api::benchmarks {
|
||||
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
template<typename T> static APIBuffer encode_message_for_proxy(const T &msg) {
|
||||
APIBuffer buffer;
|
||||
uint32_t size = msg.calculate_size();
|
||||
buffer.resize(size);
|
||||
ProtoWriteBuffer writer(&buffer, 0);
|
||||
msg.encode(writer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void escape_proxy(void *p) { asm volatile("" : : "g"(p) : "memory"); }
|
||||
|
||||
// --- ZWaveProxyFrame (Z-Wave frame, ~16 bytes payload) ---
|
||||
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
|
||||
static const uint8_t kZWaveFrameData[] = {0x01, 0x09, 0x00, 0x13, 0x01, 0x02, 0x00, 0x00,
|
||||
0x25, 0x00, 0x05, 0xC4, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
static void Encode_ZWaveProxyFrame(benchmark::State &state) {
|
||||
ZWaveProxyFrame msg;
|
||||
msg.data = kZWaveFrameData;
|
||||
msg.data_len = sizeof(kZWaveFrameData);
|
||||
APIBuffer buffer;
|
||||
buffer.resize(msg.calculate_size());
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
ProtoWriteBuffer writer(&buffer, 0);
|
||||
msg.encode(writer);
|
||||
}
|
||||
benchmark::DoNotOptimize(buffer.data());
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Encode_ZWaveProxyFrame);
|
||||
|
||||
static void Decode_ZWaveProxyFrame(benchmark::State &state) {
|
||||
ZWaveProxyFrame source;
|
||||
source.data = kZWaveFrameData;
|
||||
source.data_len = sizeof(kZWaveFrameData);
|
||||
auto encoded = encode_message_for_proxy(source);
|
||||
auto *data = encoded.data();
|
||||
auto size = encoded.size();
|
||||
benchmark::DoNotOptimize(data);
|
||||
benchmark::DoNotOptimize(size);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
ZWaveProxyFrame msg;
|
||||
escape_proxy(&msg);
|
||||
msg.decode(data, size);
|
||||
escape_proxy(&msg);
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Decode_ZWaveProxyFrame);
|
||||
|
||||
static const uint8_t kZWaveRequestData[] = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
|
||||
static void Decode_ZWaveProxyRequest(benchmark::State &state) {
|
||||
ZWaveProxyRequest source;
|
||||
source.type = enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE;
|
||||
source.data = kZWaveRequestData;
|
||||
source.data_len = sizeof(kZWaveRequestData);
|
||||
auto encoded = encode_message_for_proxy(source);
|
||||
auto *data = encoded.data();
|
||||
auto size = encoded.size();
|
||||
benchmark::DoNotOptimize(data);
|
||||
benchmark::DoNotOptimize(size);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
ZWaveProxyRequest msg;
|
||||
escape_proxy(&msg);
|
||||
msg.decode(data, size);
|
||||
escape_proxy(&msg);
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Decode_ZWaveProxyRequest);
|
||||
|
||||
#endif // USE_ZWAVE_PROXY
|
||||
|
||||
// --- SerialProxyDataReceived encode + SerialProxyWriteRequest decode ---
|
||||
//
|
||||
// SerialProxyWriteRequest is decode-only (SOURCE_CLIENT) but has the same
|
||||
// wire layout as SerialProxyDataReceived, so we encode via the latter and
|
||||
// decode as the former.
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
|
||||
static constexpr size_t kSerialPayloadSize = 64;
|
||||
static const uint8_t kSerialPayload[kSerialPayloadSize] = {
|
||||
0x55, 0xAA, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB,
|
||||
0xCD, 0xEF, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
||||
0xFF, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0,
|
||||
0xF0, 0x0F, 0x1F, 0x2F, 0x3F, 0x4F, 0x5F, 0x6F, 0x7F, 0x8F, 0x9F, 0xAF, 0xBF, 0xCF, 0xDF, 0xEF};
|
||||
|
||||
static void Encode_SerialProxyDataReceived(benchmark::State &state) {
|
||||
SerialProxyDataReceived msg;
|
||||
msg.instance = 0;
|
||||
msg.set_data(kSerialPayload, kSerialPayloadSize);
|
||||
APIBuffer buffer;
|
||||
buffer.resize(msg.calculate_size());
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
ProtoWriteBuffer writer(&buffer, 0);
|
||||
msg.encode(writer);
|
||||
}
|
||||
benchmark::DoNotOptimize(buffer.data());
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Encode_SerialProxyDataReceived);
|
||||
|
||||
static void Decode_SerialProxyWriteRequest(benchmark::State &state) {
|
||||
SerialProxyDataReceived source;
|
||||
source.instance = 0;
|
||||
source.set_data(kSerialPayload, kSerialPayloadSize);
|
||||
auto encoded = encode_message_for_proxy(source);
|
||||
auto *data = encoded.data();
|
||||
auto size = encoded.size();
|
||||
benchmark::DoNotOptimize(data);
|
||||
benchmark::DoNotOptimize(size);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
SerialProxyWriteRequest msg;
|
||||
escape_proxy(&msg);
|
||||
msg.decode(data, size);
|
||||
escape_proxy(&msg);
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Decode_SerialProxyWriteRequest);
|
||||
|
||||
#endif // USE_SERIAL_PROXY
|
||||
|
||||
// --- InfraredRFReceiveEvent encode (100 sint32 timings) +
|
||||
// InfraredRFTransmitRawTimingsRequest decode (hand-built wire bytes) ---
|
||||
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
|
||||
// Heap-allocated on first use to avoid C++17 lambda IIFE patterns that some
|
||||
// callgrind/valgrind versions handle awkwardly during benchmark init.
|
||||
static const std::vector<int32_t> &get_ir_timings_100() {
|
||||
static std::vector<int32_t> *timings = nullptr;
|
||||
if (timings == nullptr) {
|
||||
timings = new std::vector<int32_t>();
|
||||
timings->reserve(100);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
timings->push_back((i % 2 == 0) ? 560 : -560);
|
||||
}
|
||||
}
|
||||
return *timings;
|
||||
}
|
||||
|
||||
static void Encode_InfraredRFReceiveEvent(benchmark::State &state) {
|
||||
InfraredRFReceiveEvent msg;
|
||||
msg.key = 0xDEADBEEF;
|
||||
msg.timings = &get_ir_timings_100();
|
||||
APIBuffer buffer;
|
||||
buffer.resize(msg.calculate_size());
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
ProtoWriteBuffer writer(&buffer, 0);
|
||||
msg.encode(writer);
|
||||
}
|
||||
benchmark::DoNotOptimize(buffer.data());
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Encode_InfraredRFReceiveEvent);
|
||||
|
||||
static void CalculateSize_InfraredRFReceiveEvent(benchmark::State &state) {
|
||||
InfraredRFReceiveEvent msg;
|
||||
msg.key = 0xDEADBEEF;
|
||||
msg.timings = &get_ir_timings_100();
|
||||
|
||||
for (auto _ : state) {
|
||||
uint32_t result = 0;
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
result += msg.calculate_size();
|
||||
}
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(CalculateSize_InfraredRFReceiveEvent);
|
||||
|
||||
// Hand-built wire bytes for InfraredRFTransmitRawTimingsRequest (decode-only,
|
||||
// no sister message with identical wire layout).
|
||||
// field 2 (key, fixed32): tag=0x15, 4 LE bytes
|
||||
// field 3 (carrier_frequency): tag=0x18, varint
|
||||
// field 4 (repeat_count): tag=0x20, varint
|
||||
// field 5 (timings, packed sint32): tag=0x2A, length varint, packed payload
|
||||
// field 6 (modulation): tag=0x30, varint
|
||||
static APIBuffer build_infrared_rf_transmit_wire() {
|
||||
uint8_t bytes[256];
|
||||
size_t len = 0;
|
||||
|
||||
auto put_byte = [&](uint8_t b) { bytes[len++] = b; };
|
||||
auto put_varint = [&](uint32_t v) {
|
||||
while (v >= 0x80) {
|
||||
bytes[len++] = static_cast<uint8_t>((v & 0x7F) | 0x80);
|
||||
v >>= 7;
|
||||
}
|
||||
bytes[len++] = static_cast<uint8_t>(v);
|
||||
};
|
||||
auto encode_zigzag = [](int32_t v) -> uint32_t {
|
||||
return (static_cast<uint32_t>(v) << 1) ^ static_cast<uint32_t>(v >> 31);
|
||||
};
|
||||
|
||||
put_byte(0x15);
|
||||
put_byte(0xEF);
|
||||
put_byte(0xBE);
|
||||
put_byte(0xAD);
|
||||
put_byte(0xDE);
|
||||
put_byte(0x18);
|
||||
put_varint(38000);
|
||||
put_byte(0x20);
|
||||
put_varint(2);
|
||||
|
||||
uint8_t packed[200];
|
||||
size_t packed_len = 0;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int32_t value = (i % 2 == 0) ? 560 : -560;
|
||||
uint32_t zz = encode_zigzag(value);
|
||||
while (zz >= 0x80) {
|
||||
packed[packed_len++] = static_cast<uint8_t>((zz & 0x7F) | 0x80);
|
||||
zz >>= 7;
|
||||
}
|
||||
packed[packed_len++] = static_cast<uint8_t>(zz);
|
||||
}
|
||||
put_byte(0x2A);
|
||||
put_varint(static_cast<uint32_t>(packed_len));
|
||||
std::memcpy(bytes + len, packed, packed_len);
|
||||
len += packed_len;
|
||||
|
||||
APIBuffer buf;
|
||||
buf.resize(len);
|
||||
std::memcpy(buf.data(), bytes, len);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void Decode_InfraredRFTransmitRawTimingsRequest(benchmark::State &state) {
|
||||
auto encoded = build_infrared_rf_transmit_wire();
|
||||
auto *data = encoded.data();
|
||||
auto size = encoded.size();
|
||||
benchmark::DoNotOptimize(data);
|
||||
benchmark::DoNotOptimize(size);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
InfraredRFTransmitRawTimingsRequest msg;
|
||||
escape_proxy(&msg);
|
||||
msg.decode(data, size);
|
||||
escape_proxy(&msg);
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Decode_InfraredRFTransmitRawTimingsRequest);
|
||||
|
||||
#endif // USE_IR_RF || USE_RADIO_FREQUENCY
|
||||
|
||||
} // namespace esphome::api::benchmarks
|
||||
Reference in New Issue
Block a user