diff --git a/tests/benchmarks/components/api/bench_proto_decode.cpp b/tests/benchmarks/components/api/bench_proto_decode.cpp index 0951c521be8..961c629f2a9 100644 --- a/tests/benchmarks/components/api/bench_proto_decode.cpp +++ b/tests/benchmarks/components/api/bench_proto_decode.cpp @@ -1,7 +1,5 @@ #include -#include - #include "esphome/components/api/api_pb2.h" #include "esphome/components/api/api_buffer.h" @@ -79,188 +77,6 @@ static void Decode_SwitchCommandRequest(benchmark::State &state) { } BENCHMARK(Decode_SwitchCommandRequest); -// --- ZWaveProxyFrame decode (~16-byte data buffer) --- - -#ifdef USE_ZWAVE_PROXY - -static void Decode_ZWaveProxyFrame(benchmark::State &state) { - static const uint8_t frame_data[] = {0x01, 0x09, 0x00, 0x13, 0x01, 0x02, 0x00, 0x00, - 0x25, 0x00, 0x05, 0xC4, 0x00, 0x00, 0x00, 0x00}; - ZWaveProxyFrame source; - source.data = frame_data; - source.data_len = sizeof(frame_data); - auto encoded = encode_message(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(&msg); - msg.decode(data, size); - escape(&msg); - } - } - state.SetItemsProcessed(state.iterations() * kInnerIterations); -} -BENCHMARK(Decode_ZWaveProxyFrame); - -static void Decode_ZWaveProxyRequest(benchmark::State &state) { - static const uint8_t req_data[] = {0xDE, 0xAD, 0xBE, 0xEF}; - ZWaveProxyRequest source; - source.type = enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE; - source.data = req_data; - source.data_len = sizeof(req_data); - auto encoded = encode_message(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(&msg); - msg.decode(data, size); - escape(&msg); - } - } - state.SetItemsProcessed(state.iterations() * kInnerIterations); -} -BENCHMARK(Decode_ZWaveProxyRequest); - -#endif // USE_ZWAVE_PROXY - -// --- SerialProxyWriteRequest decode (instance + 64-byte data) --- -// -// SerialProxyWriteRequest is decode-only (SOURCE_CLIENT), so we encode via -// SerialProxyDataReceived which has identical wire format -// (uint32 instance = 1; bytes data = 2;). - -#ifdef USE_SERIAL_PROXY - -static void Decode_SerialProxyWriteRequest(benchmark::State &state) { - static constexpr size_t kPayloadSize = 64; - static uint8_t payload[kPayloadSize]; - for (size_t i = 0; i < kPayloadSize; i++) - payload[i] = static_cast(i); - - SerialProxyDataReceived source; - source.instance = 0; - source.set_data(payload, kPayloadSize); - auto encoded = encode_message(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(&msg); - msg.decode(data, size); - escape(&msg); - } - } - state.SetItemsProcessed(state.iterations() * kInnerIterations); -} -BENCHMARK(Decode_SerialProxyWriteRequest); - -#endif // USE_SERIAL_PROXY - -// --- InfraredRFTransmitRawTimingsRequest decode (100 zigzag-encoded timings) --- -// -// Hand-built wire bytes since this message is decode-only and has no sister -// type with an identical layout. Wire format: -// 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 - -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) - -static APIBuffer build_infrared_rf_transmit_wire() { - // Build the entire wire payload into a stack buffer, then copy into the - // returned APIBuffer in a single resize+memcpy. Keeps allocation count - // low so callgrind/valgrind doesn't churn through hundreds of grow_()s. - 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((v & 0x7F) | 0x80); - v >>= 7; - } - bytes[len++] = static_cast(v); - }; - auto encode_zigzag = [](int32_t v) -> uint32_t { - return (static_cast(v) << 1) ^ static_cast(v >> 31); - }; - - // field 2: key (fixed32) = 0xDEADBEEF - put_byte(0x15); - put_byte(0xEF); - put_byte(0xBE); - put_byte(0xAD); - put_byte(0xDE); - // field 3: carrier_frequency = 38000 - put_byte(0x18); - put_varint(38000); - // field 4: repeat_count = 2 - put_byte(0x20); - put_varint(2); - // field 5: timings (packed sint32) — 100 entries alternating mark/space. - // Each entry encodes to 2 bytes (zigzag(560)=1120 → varint 0xE0 0x08), so - // packed payload is 200 bytes; with tag (1) + length varint (2) it fits in - // the 256-byte stack buffer. - 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((zz & 0x7F) | 0x80); - zz >>= 7; - } - packed[packed_len++] = static_cast(zz); - } - put_byte(0x2A); - put_varint(static_cast(packed_len)); - std::memcpy(bytes + len, packed, packed_len); - len += packed_len; - // field 6: modulation = 0 — skip (default value, not encoded by senders) - - 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(&msg); - msg.decode(data, size); - escape(&msg); - } - } - state.SetItemsProcessed(state.iterations() * kInnerIterations); -} -BENCHMARK(Decode_InfraredRFTransmitRawTimingsRequest); - -#endif // USE_IR_RF || USE_RADIO_FREQUENCY - // --- LightCommandRequest decode (complex command with many fields) --- static void Decode_LightCommandRequest(benchmark::State &state) { diff --git a/tests/benchmarks/components/api/bench_proto_encode.cpp b/tests/benchmarks/components/api/bench_proto_encode.cpp index d1f85574f9d..1e2efcd2813 100644 --- a/tests/benchmarks/components/api/bench_proto_encode.cpp +++ b/tests/benchmarks/components/api/bench_proto_encode.cpp @@ -384,128 +384,4 @@ BENCHMARK(CalcAndEncode_BLERawAdvs12_Fresh); #endif // USE_BLUETOOTH_PROXY -// --- ZWaveProxyFrame (Z-Wave frame, ~16 bytes payload) --- - -#ifdef USE_ZWAVE_PROXY - -static constexpr uint8_t kZWaveFrameData[] = {0x01, 0x09, 0x00, 0x13, 0x01, 0x02, 0x00, 0x00, - 0x25, 0x00, 0x05, 0xC4, 0x00, 0x00, 0x00, 0x00}; - -static ZWaveProxyFrame make_zwave_proxy_frame() { - ZWaveProxyFrame msg; - msg.data = kZWaveFrameData; - msg.data_len = sizeof(kZWaveFrameData); - return msg; -} - -static void Encode_ZWaveProxyFrame(benchmark::State &state) { - auto msg = make_zwave_proxy_frame(); - 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); - -#endif // USE_ZWAVE_PROXY - -// --- SerialProxyDataReceived (serial passthrough, 64-byte payload) --- - -#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 SerialProxyDataReceived make_serial_proxy_data_received() { - SerialProxyDataReceived msg; - msg.instance = 0; - msg.set_data(kSerialPayload, kSerialPayloadSize); - return msg; -} - -static void Encode_SerialProxyDataReceived(benchmark::State &state) { - auto msg = make_serial_proxy_data_received(); - 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); - -#endif // USE_SERIAL_PROXY - -// --- InfraredRFReceiveEvent (100 timings, typical IR/RF capture) --- - -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) - -// Mark/space pairs simulating a typical RC-5 / NEC capture (100 timings). -static const std::vector &get_ir_timings_100() { - static std::vector *timings = nullptr; - if (timings == nullptr) { - timings = new std::vector(); - timings->reserve(100); - for (int i = 0; i < 100; i++) { - timings->push_back((i % 2 == 0) ? 560 : -560); - } - } - return *timings; -} - -static InfraredRFReceiveEvent make_infrared_rf_receive_event() { - InfraredRFReceiveEvent msg; - msg.key = 0xDEADBEEF; - msg.timings = &get_ir_timings_100(); - return msg; -} - -static void Encode_InfraredRFReceiveEvent(benchmark::State &state) { - auto msg = make_infrared_rf_receive_event(); - 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) { - auto msg = make_infrared_rf_receive_event(); - - 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); - -#endif // USE_IR_RF || USE_RADIO_FREQUENCY - } // namespace esphome::api::benchmarks diff --git a/tests/benchmarks/components/api/bench_proto_proxy.cpp b/tests/benchmarks/components/api/bench_proto_proxy.cpp new file mode 100644 index 00000000000..612d8e69f3b --- /dev/null +++ b/tests/benchmarks/components/api/bench_proto_proxy.cpp @@ -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 + +#include + +#include "esphome/components/api/api_pb2.h" +#include "esphome/components/api/api_buffer.h" + +namespace esphome::api::benchmarks { + +static constexpr int kInnerIterations = 2000; + +template 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 &get_ir_timings_100() { + static std::vector *timings = nullptr; + if (timings == nullptr) { + timings = new std::vector(); + 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((v & 0x7F) | 0x80); + v >>= 7; + } + bytes[len++] = static_cast(v); + }; + auto encode_zigzag = [](int32_t v) -> uint32_t { + return (static_cast(v) << 1) ^ static_cast(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((zz & 0x7F) | 0x80); + zz >>= 7; + } + packed[packed_len++] = static_cast(zz); + } + put_byte(0x2A); + put_varint(static_cast(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