From 8c058cd7257df1864cbff9dac4b8ff90f6e48f7d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 17 Mar 2026 00:20:52 -1000 Subject: [PATCH] Replace hand-encoded protobuf bytes with programmatic encoding in decode benchmarks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Encode messages once in setup using the real protobuf API, then decode the resulting bytes in the benchmark loop. This keeps decode benchmarks automatically in sync with the protobuf schema — hand-encoded byte arrays would silently break when fields change. --- .../components/api/bench_proto_decode.cpp | 116 ++++++------------ 1 file changed, 38 insertions(+), 78 deletions(-) diff --git a/tests/benchmarks/components/api/bench_proto_decode.cpp b/tests/benchmarks/components/api/bench_proto_decode.cpp index a5ecf78cde3..113201dd8a1 100644 --- a/tests/benchmarks/components/api/bench_proto_decode.cpp +++ b/tests/benchmarks/components/api/bench_proto_decode.cpp @@ -10,24 +10,32 @@ namespace esphome::api::benchmarks { // sub-microsecond benchmarks. static constexpr int kInnerIterations = 2000; +// Helper: encode a message into a buffer and return it. +// Benchmarks encode once in setup, then decode the resulting bytes in a loop. +// This keeps decode benchmarks in sync with the actual protobuf schema — +// hand-encoded byte arrays would silently break when fields change. +template static APIBuffer encode_message(const T &msg) { + APIBuffer buffer; + uint32_t size = msg.calculate_size(); + buffer.resize(size); + ProtoWriteBuffer writer(&buffer, 0); + msg.encode(writer); + return buffer; +} + // --- HelloRequest decode (string + varint fields) --- static void Decode_HelloRequest(benchmark::State &state) { - // Manually encoded HelloRequest: - // field 1 (string): "aioesphomeapi" - // field 2 (varint): 1 (api_version_major) - // field 3 (varint): 10 (api_version_minor) - uint8_t encoded[] = { - 0x0A, 0x0D, // field 1, length 13 - 'a', 'i', 'o', 'e', 's', 'p', 'h', 'o', 'm', 'e', 'a', 'p', 'i', // "aioesphomeapi" - 0x10, 0x01, // field 2, value 1 - 0x18, 0x0A, // field 3, value 10 - }; + HelloRequest source; + source.client_info = StringRef::from_lit("aioesphomeapi"); + source.api_version_major = 1; + source.api_version_minor = 10; + auto encoded = encode_message(source); for (auto _ : state) { HelloRequest msg; for (int i = 0; i < kInnerIterations; i++) { - msg.decode(encoded, sizeof(encoded)); + msg.decode(encoded.data(), encoded.size()); } benchmark::DoNotOptimize(msg.api_version_major); } @@ -38,17 +46,15 @@ BENCHMARK(Decode_HelloRequest); // --- SwitchCommandRequest decode (simple command) --- static void Decode_SwitchCommandRequest(benchmark::State &state) { - // field 1 (fixed32): key = 0x12345678 - // field 2 (varint): state = true - uint8_t encoded[] = { - 0x0D, 0x78, 0x56, 0x34, 0x12, // field 1, fixed32 - 0x10, 0x01, // field 2, varint true - }; + SwitchCommandRequest source; + source.key = 0x12345678; + source.state = true; + auto encoded = encode_message(source); for (auto _ : state) { SwitchCommandRequest msg; for (int i = 0; i < kInnerIterations; i++) { - msg.decode(encoded, sizeof(encoded)); + msg.decode(encoded.data(), encoded.size()); } benchmark::DoNotOptimize(msg.state); } @@ -59,70 +65,24 @@ BENCHMARK(Decode_SwitchCommandRequest); // --- LightCommandRequest decode (complex command with many fields) --- static void Decode_LightCommandRequest(benchmark::State &state) { - uint8_t encoded[] = { - // field 1: key (fixed32) = 0x11223344 - 0x0D, - 0x44, - 0x33, - 0x22, - 0x11, - // field 2: has_state (varint) = true - 0x10, - 0x01, - // field 3: state (varint) = true - 0x18, - 0x01, - // field 4: has_brightness (varint) = true - 0x20, - 0x01, - // field 5: brightness (fixed32/float) = 0.8 - 0x2D, - 0xCD, - 0xCC, - 0x4C, - 0x3F, - // field 9: has_rgb (varint) = true - 0x48, - 0x01, - // field 10: red (fixed32/float) = 1.0 - 0x55, - 0x00, - 0x00, - 0x80, - 0x3F, - // field 11: green (fixed32/float) = 0.5 - 0x5D, - 0x00, - 0x00, - 0x00, - 0x3F, - // field 12: blue (fixed32/float) = 0.2 - 0x65, - 0xCD, - 0xCC, - 0x4C, - 0x3E, - // field 20: has_effect (varint) = true - 0xA0, - 0x01, - 0x01, - // field 21: effect (string) = "rainbow" - 0xAA, - 0x01, - 0x07, - 'r', - 'a', - 'i', - 'n', - 'b', - 'o', - 'w', - }; + LightCommandRequest source; + source.key = 0x11223344; + source.has_state = true; + source.state = true; + source.has_brightness = true; + source.brightness = 0.8f; + source.has_rgb = true; + source.red = 1.0f; + source.green = 0.5f; + source.blue = 0.2f; + source.has_effect = true; + source.effect = StringRef::from_lit("rainbow"); + auto encoded = encode_message(source); for (auto _ : state) { LightCommandRequest msg; for (int i = 0; i < kInnerIterations; i++) { - msg.decode(encoded, sizeof(encoded)); + msg.decode(encoded.data(), encoded.size()); } benchmark::DoNotOptimize(msg.brightness); }