mirror of
https://github.com/esphome/esphome.git
synced 2026-11-15 09:36:04 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
890c300125 | ||
|
|
a5bb8a0cbc | ||
|
|
ecd14a2ea7 | ||
|
|
a9bf6f9f10 | ||
|
|
1fbd657cbc | ||
|
|
0f1899e921 |
No files matched your search
@@ -25,9 +25,6 @@ CORE_BENCHMARKS_DIR: Path = Path(root_path) / "tests" / "benchmarks" / "core"
|
||||
# allow benchmarks to compile on the host platform.
|
||||
STUBS_DIR: Path = Path(root_path) / "tests" / "benchmarks" / "stubs"
|
||||
|
||||
# Shared test doubles, reached by -I so only the benchmarks that include them pull in their headers
|
||||
HELPERS_DIR: Path = Path(root_path) / "tests" / "benchmarks" / "helpers"
|
||||
|
||||
BUILD_FLAGS = [
|
||||
"-Os", # match firmware optimization level (detects inlining regressions)
|
||||
"-g", # debug symbols for profiling
|
||||
@@ -35,7 +32,6 @@ BUILD_FLAGS = [
|
||||
"-fdata-sections", # required for dead-code stripping with -Os
|
||||
"-DUSE_BENCHMARK", # disable WarnIfComponentBlockingGuard in finish()
|
||||
f"-I{STUBS_DIR}", # stub headers for ESP32-only components
|
||||
f"-I{HELPERS_DIR}", # shared test doubles such as fake_uart.h
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
from tests.testing_helpers import ComponentManifestOverride, add_defines_to_code
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
manifest.enable_codegen()
|
||||
# The filter benchmarks need filter.cpp, which is only built with this define
|
||||
add_defines_to_code(manifest, "USE_BINARY_SENSOR_FILTER")
|
||||
@@ -0,0 +1,101 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/binary_sensor/filter.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome::binary_sensor::benchmarks {
|
||||
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
// Long enough that no delay fires during a run, so every publish is a bounce
|
||||
static constexpr uint32_t DELAY_MS = 3600000;
|
||||
|
||||
// Alternate the input with one scheduler pass per publish, like a bouncing GPIO polled each loop
|
||||
static void publish_bounce(benchmark::State &state, BinarySensor &sensor, bool run_scheduler, bool bounce) {
|
||||
sensor.publish_initial_state(false);
|
||||
const uint32_t now = millis();
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(bounce && i % 2 == 0);
|
||||
if (run_scheduler)
|
||||
App.scheduler.call(now);
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
// Resetting the filters cancels their pending timers before the filter leaves scope
|
||||
sensor.invalidate_state();
|
||||
App.scheduler.call(now);
|
||||
}
|
||||
|
||||
// Benchmark: no filter, unchanged input; subtract from the delayed filters that hold their output
|
||||
static void BinarySensorFilter_SchedulerBaseline(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
publish_bounce(state, sensor, /*run_scheduler=*/true, /*bounce=*/false);
|
||||
}
|
||||
BENCHMARK(BinarySensorFilter_SchedulerBaseline);
|
||||
|
||||
// Benchmark: invert, no scheduler pass since it schedules nothing, so not comparable to the five below
|
||||
static void BinarySensorFilter_Invert(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
InvertFilter filter;
|
||||
sensor.add_filter(&filter);
|
||||
publish_bounce(state, sensor, /*run_scheduler=*/false, /*bounce=*/true);
|
||||
}
|
||||
BENCHMARK(BinarySensorFilter_Invert);
|
||||
|
||||
// Benchmark: delayed_on bounce, set_timeout on press and cancel on release
|
||||
static void BinarySensorFilter_DelayedOn(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
DelayedOnFilter filter;
|
||||
filter.set_delay([]() -> uint32_t { return DELAY_MS; });
|
||||
sensor.add_filter(&filter);
|
||||
publish_bounce(state, sensor, /*run_scheduler=*/true, /*bounce=*/true);
|
||||
}
|
||||
BENCHMARK(BinarySensorFilter_DelayedOn);
|
||||
|
||||
// Benchmark: delayed_off bounce, cancel on press and set_timeout on release
|
||||
static void BinarySensorFilter_DelayedOff(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
DelayedOffFilter filter;
|
||||
filter.set_delay([]() -> uint32_t { return DELAY_MS; });
|
||||
sensor.add_filter(&filter);
|
||||
publish_bounce(state, sensor, /*run_scheduler=*/true, /*bounce=*/true);
|
||||
}
|
||||
BENCHMARK(BinarySensorFilter_DelayedOff);
|
||||
|
||||
// Same shape as the function codegen emits for delayed_on_off with time_on and time_off
|
||||
static optional<bool> delayed_on_off_fn(Filter *filter, bool x) {
|
||||
return delay_output(filter, x ? DELAY_MS : DELAY_MS / 2, x);
|
||||
}
|
||||
|
||||
// Benchmark: delayed_on_off bounce, every edge replaces the pending timeout
|
||||
static void BinarySensorFilter_DelayedOnOff(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
ApplyFilter<delayed_on_off_fn> filter;
|
||||
sensor.add_filter(&filter);
|
||||
publish_bounce(state, sensor, /*run_scheduler=*/true, /*bounce=*/true);
|
||||
}
|
||||
BENCHMARK(BinarySensorFilter_DelayedOnOff);
|
||||
|
||||
// Benchmark: settle bounce, every edge re-arms the timeout
|
||||
static void BinarySensorFilter_Settle(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
SettleFilter filter;
|
||||
filter.set_delay([]() -> uint32_t { return DELAY_MS; });
|
||||
sensor.add_filter(&filter);
|
||||
publish_bounce(state, sensor, /*run_scheduler=*/true, /*bounce=*/true);
|
||||
}
|
||||
BENCHMARK(BinarySensorFilter_Settle);
|
||||
|
||||
// Benchmark: autorepeat, passes every press and release, so it also pays a state change per publish
|
||||
static void BinarySensorFilter_Autorepeat(benchmark::State &state) {
|
||||
BinarySensor sensor;
|
||||
AutorepeatFilter<1> filter({{DELAY_MS, 100, 900}});
|
||||
sensor.add_filter(&filter);
|
||||
publish_bounce(state, sensor, /*run_scheduler=*/true, /*bounce=*/true);
|
||||
}
|
||||
BENCHMARK(BinarySensorFilter_Autorepeat);
|
||||
|
||||
} // namespace esphome::binary_sensor::benchmarks
|
||||
@@ -1,76 +0,0 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/ld2450/ld2450.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "fake_uart.h"
|
||||
|
||||
namespace esphome::ld2450::benchmarks {
|
||||
|
||||
using esphome::benchmarks::FakeUART;
|
||||
|
||||
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
static constexpr size_t FRAME_SIZE = 30;
|
||||
|
||||
// Periodic frames with one moving target at two positions
|
||||
static constexpr uint8_t FRAME_A[FRAME_SIZE] = {0xAA, 0xFF, 0x03, 0x00, 0x0E, 0x03, 0xB1, 0x86, 0x10, 0x00,
|
||||
0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xCC};
|
||||
static constexpr uint8_t FRAME_B[FRAME_SIZE] = {0xAA, 0xFF, 0x03, 0x00, 0x2A, 0x03, 0xC8, 0x86, 0x10, 0x00,
|
||||
0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xCC};
|
||||
|
||||
// --- loop() parsing one periodic frame, no entities attached ---
|
||||
|
||||
static void LD2450_PeriodicFrame(benchmark::State &state) {
|
||||
FakeUART uart;
|
||||
LD2450Component radar;
|
||||
radar.set_uart_parent(&uart);
|
||||
radar.set_link_timeout(60000);
|
||||
// Bring the link up outside the timed loop
|
||||
uart.load(FRAME_A, FRAME_SIZE);
|
||||
radar.loop();
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
uart.load(FRAME_A, FRAME_SIZE);
|
||||
radar.loop();
|
||||
}
|
||||
benchmark::DoNotOptimize(uart.available());
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(LD2450_PeriodicFrame);
|
||||
|
||||
// --- loop() parsing a moving target and publishing its sensors ---
|
||||
|
||||
static void LD2450_PeriodicFrameSensors(benchmark::State &state) {
|
||||
FakeUART uart;
|
||||
LD2450Component radar;
|
||||
radar.set_uart_parent(&uart);
|
||||
radar.set_link_timeout(60000);
|
||||
sensor::Sensor x, y, speed, distance, angle, count;
|
||||
binary_sensor::BinarySensor presence;
|
||||
radar.set_move_x_sensor(0, &x);
|
||||
radar.set_move_y_sensor(0, &y);
|
||||
radar.set_move_speed_sensor(0, &speed);
|
||||
radar.set_move_distance_sensor(0, &distance);
|
||||
radar.set_move_angle_sensor(0, &angle);
|
||||
radar.set_target_count_sensor(&count);
|
||||
radar.set_target_binary_sensor(&presence);
|
||||
uart.load(FRAME_A, FRAME_SIZE);
|
||||
radar.loop();
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
uart.load((i & 1) ? FRAME_B : FRAME_A, FRAME_SIZE);
|
||||
radar.loop();
|
||||
}
|
||||
benchmark::DoNotOptimize(x.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(LD2450_PeriodicFrameSensors);
|
||||
|
||||
} // namespace esphome::ld2450::benchmarks
|
||||
@@ -1,2 +0,0 @@
|
||||
sensor:
|
||||
binary_sensor:
|
||||
@@ -1,67 +0,0 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "fake_uart.h"
|
||||
|
||||
namespace esphome::modbus::benchmarks {
|
||||
|
||||
using esphome::benchmarks::FakeUART;
|
||||
|
||||
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
static constexpr uint8_t ADDRESS = 1;
|
||||
static constexpr uint8_t REGISTER_COUNT = 10;
|
||||
// Read 10 holding registers from address 0, as an energy meter poll does
|
||||
static constexpr uint8_t REQUEST_PDU[] = {0x03, 0x00, 0x00, 0x00, REGISTER_COUNT};
|
||||
// Address + function + byte count + 20 data bytes + CRC
|
||||
static constexpr size_t RESPONSE_SIZE = 3 + REGISTER_COUNT * 2 + 2;
|
||||
|
||||
class SumDevice : public ModbusClientDevice {
|
||||
public:
|
||||
using ModbusClientDevice::ModbusClientDevice;
|
||||
void on_read_holding_registers(uint16_t start_address, std::span<const uint16_t> registers,
|
||||
ResponseStatus status) override {
|
||||
for (uint16_t value : registers)
|
||||
this->sum += value;
|
||||
}
|
||||
uint32_t sum{0};
|
||||
};
|
||||
|
||||
class NoDelayHub : public ModbusClientHub {
|
||||
protected:
|
||||
// No wait before a send, so every loop() can send the next poll
|
||||
int32_t tx_delay_remaining() override { return 0; }
|
||||
};
|
||||
|
||||
// --- One continuous poll cycle per loop(): receive and CRC check the response, decode it, requeue and resend ---
|
||||
|
||||
static void Modbus_ReadHoldingPollCycle(benchmark::State &state) {
|
||||
// Address 1, function 3, byte count 20, 10 registers; the CRC goes in the last two bytes
|
||||
uint8_t response[RESPONSE_SIZE] = {0x01, 0x03, 0x14, 0x09, 0x1F, 0x00, 0x00, 0x01, 0xF4, 0x00, 0x00, 0x13,
|
||||
0x88, 0x00, 0x32, 0x03, 0xE8, 0x00, 0x00, 0x00, 0x64, 0x12, 0x34};
|
||||
put_le16(&response[RESPONSE_SIZE - 2], crc16(response, RESPONSE_SIZE - 2));
|
||||
|
||||
FakeUART uart;
|
||||
uart.set_baud_rate(9600);
|
||||
NoDelayHub hub;
|
||||
hub.set_uart_parent(&uart);
|
||||
hub.setup();
|
||||
SumDevice device(&hub, ADDRESS);
|
||||
hub.queue_pdu(ADDRESS, REQUEST_PDU, &device, {.continuous = true});
|
||||
// The first loop() sends the poll; every send rewinds the response, so the device answers each one
|
||||
hub.loop();
|
||||
uart.load(response, RESPONSE_SIZE);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
hub.loop();
|
||||
}
|
||||
benchmark::DoNotOptimize(device.sum);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Modbus_ReadHoldingPollCycle);
|
||||
|
||||
} // namespace esphome::modbus::benchmarks
|
||||
@@ -2,10 +2,57 @@
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/sensor/filter.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
namespace esphome::sensor::benchmarks {
|
||||
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
// Long enough that interval filters never fire during a run
|
||||
static constexpr uint32_t NEVER_FIRE_MS = 3600000;
|
||||
|
||||
static constexpr float NO_MAX_DELTA = std::numeric_limits<float>::infinity();
|
||||
static constexpr uint32_t LOOP_TICK_MS = 16;
|
||||
|
||||
static inline void advance_ramp(float &value) {
|
||||
value += 0.1f;
|
||||
if (value > 1000.0f)
|
||||
value = 0.0f;
|
||||
}
|
||||
|
||||
// Publish a 0..1000 ramp in 0.1 steps
|
||||
static void publish_ramp(benchmark::State &state, Sensor &sensor) {
|
||||
float value = 0.0f;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(value);
|
||||
advance_ramp(value);
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
|
||||
// Publish the ramp one loop tick apart from t=1, then restore the cached loop time for later benchmarks
|
||||
static void publish_ramp_ticked(benchmark::State &state, Sensor &sensor) {
|
||||
const uint32_t saved = App.get_loop_component_start_time();
|
||||
uint32_t now = 1;
|
||||
float value = 0.0f;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
LoopBlockingGuard tick{nullptr, nullptr, now};
|
||||
sensor.publish_state(value);
|
||||
now += LOOP_TICK_MS;
|
||||
advance_ramp(value);
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
LoopBlockingGuard restore{nullptr, nullptr, saved};
|
||||
}
|
||||
|
||||
// Benchmark: sensor publish through a SlidingWindowMovingAverageFilter (window=5, send_every=1)
|
||||
static void SensorFilter_SlidingWindowAvg(benchmark::State &state) {
|
||||
@@ -15,17 +62,7 @@ static void SensorFilter_SlidingWindowAvg(benchmark::State &state) {
|
||||
auto *filter = new SlidingWindowMovingAverageFilter(5, 1, 1);
|
||||
sensor.add_filter(filter);
|
||||
|
||||
float value = 0.0f;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(value);
|
||||
value += 0.1f;
|
||||
if (value > 1000.0f)
|
||||
value = 0.0f;
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
publish_ramp(state, sensor);
|
||||
}
|
||||
BENCHMARK(SensorFilter_SlidingWindowAvg);
|
||||
|
||||
@@ -37,17 +74,7 @@ static void SensorFilter_ExponentialMovingAvg(benchmark::State &state) {
|
||||
auto *filter = new ExponentialMovingAverageFilter(0.1f, 1, 1);
|
||||
sensor.add_filter(filter);
|
||||
|
||||
float value = 0.0f;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(value);
|
||||
value += 0.1f;
|
||||
if (value > 1000.0f)
|
||||
value = 0.0f;
|
||||
}
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
publish_ramp(state, sensor);
|
||||
}
|
||||
BENCHMARK(SensorFilter_ExponentialMovingAvg);
|
||||
|
||||
@@ -61,18 +88,97 @@ static void SensorFilter_Chain3(benchmark::State &state) {
|
||||
new SlidingWindowMovingAverageFilter(5, 1, 1),
|
||||
});
|
||||
|
||||
float value = 0.0f;
|
||||
publish_ramp(state, sensor);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Chain3);
|
||||
|
||||
// Benchmark: throttle 1s, one publish per 16ms loop tick (1 in 63 passes)
|
||||
static void SensorFilter_Throttle(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
sensor.add_filter(new ThrottleFilter(1000));
|
||||
publish_ramp_ticked(state, sensor);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Throttle);
|
||||
|
||||
// Benchmark: delta 0.5 on a 0.1 ramp (1 in 5 passes)
|
||||
static void SensorFilter_Delta(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
sensor.add_filter(new DeltaFilter(0.5f, 0.0f, NO_MAX_DELTA, 0.0f));
|
||||
publish_ramp(state, sensor);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Delta);
|
||||
|
||||
// Fixed zig-zag so the median window is rarely already sorted
|
||||
static constexpr float MEDIAN_INPUT[] = {3.0f, 9.0f, 1.0f, 7.0f, 4.0f, 8.0f, 2.0f, 6.0f,
|
||||
5.0f, 0.0f, 9.5f, 1.5f, 7.5f, 3.5f, 8.5f, 2.5f};
|
||||
|
||||
// Benchmark: median of 5, computed on every publish
|
||||
static void SensorFilter_Median(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
sensor.add_filter(new MedianFilter(5, 1, 1));
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
sensor.publish_state(value);
|
||||
value += 0.1f;
|
||||
if (value > 1000.0f)
|
||||
value = 0.0f;
|
||||
}
|
||||
for (int i = 0; i < kInnerIterations; i++)
|
||||
sensor.publish_state(MEDIAN_INPUT[i % std::size(MEDIAN_INPUT)]);
|
||||
benchmark::DoNotOptimize(sensor.state);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Chain3);
|
||||
BENCHMARK(SensorFilter_Median);
|
||||
|
||||
// Cancel the interval so it never fires into the sensor after it leaves scope
|
||||
static void publish_ramp_then_cancel(benchmark::State &state, Sensor &sensor, Filter *filter) {
|
||||
publish_ramp(state, sensor);
|
||||
App.scheduler.cancel_interval(filter);
|
||||
App.scheduler.call(millis());
|
||||
}
|
||||
|
||||
// Benchmark: throttle_average accumulate path (interval never fires)
|
||||
static void SensorFilter_ThrottleAverage(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
auto *filter = new ThrottleAverageFilter(NEVER_FIRE_MS);
|
||||
sensor.add_filter(filter);
|
||||
publish_ramp_then_cancel(state, sensor, filter);
|
||||
}
|
||||
BENCHMARK(SensorFilter_ThrottleAverage);
|
||||
|
||||
// Benchmark: heartbeat store path (interval never fires)
|
||||
static void SensorFilter_Heartbeat(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
auto *filter = new HeartbeatFilter(NEVER_FIRE_MS);
|
||||
sensor.add_filter(filter);
|
||||
publish_ramp_then_cancel(state, sensor, filter);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Heartbeat);
|
||||
|
||||
// Same shape as the function codegen emits for a lambda filter
|
||||
static optional<float> lambda_filter_fn(float x) {
|
||||
if (x < 0.0f)
|
||||
return {};
|
||||
return x * 1.8f + 32.0f;
|
||||
}
|
||||
|
||||
// Benchmark: lambda filter as generated (ApplyFilter)
|
||||
static void SensorFilter_Lambda(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
sensor.add_filter(new ApplyFilter<lambda_filter_fn>());
|
||||
publish_ramp(state, sensor);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Lambda);
|
||||
|
||||
// Benchmark: round to 1 decimal
|
||||
static void SensorFilter_Round(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
sensor.add_filter(new RoundFilter(1));
|
||||
publish_ramp(state, sensor);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Round);
|
||||
|
||||
// Benchmark: throttle 60s or delta 0.5 ("on change or every minute"), one publish per tick
|
||||
static void SensorFilter_Or(benchmark::State &state) {
|
||||
Sensor sensor;
|
||||
sensor.add_filter(new OrFilter<2>(new ThrottleFilter(60000), new DeltaFilter(0.5f, 0.0f, NO_MAX_DELTA, 0.0f)));
|
||||
publish_ramp_ticked(state, sensor);
|
||||
}
|
||||
BENCHMARK(SensorFilter_Or);
|
||||
|
||||
} // namespace esphome::sensor::benchmarks
|
||||
@@ -1,57 +0,0 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "fake_uart.h"
|
||||
|
||||
namespace esphome::uart::benchmarks {
|
||||
|
||||
using esphome::benchmarks::FakeUART;
|
||||
|
||||
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
// One second of a GPS module's NMEA output
|
||||
static constexpr char NMEA_BURST[] = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n"
|
||||
"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n"
|
||||
"$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n"
|
||||
"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n"
|
||||
"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
|
||||
|
||||
// --- read_available(): drain a whole NMEA burst through a byte-sum callback ---
|
||||
|
||||
static void UART_ReadAvailable_NmeaBurst(benchmark::State &state) {
|
||||
FakeUART uart;
|
||||
UARTDevice dev(&uart);
|
||||
uint32_t sum = 0;
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
uart.load(reinterpret_cast<const uint8_t *>(NMEA_BURST), sizeof(NMEA_BURST) - 1);
|
||||
dev.read_available([&](uint8_t b) { sum += b; });
|
||||
}
|
||||
benchmark::DoNotOptimize(sum);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(UART_ReadAvailable_NmeaBurst);
|
||||
|
||||
// --- discard_available(): drop a 256 byte backlog ---
|
||||
|
||||
// Two virtual calls per byte, available() and read_byte(): this measures that per-byte call overhead
|
||||
static void UART_DiscardAvailable(benchmark::State &state) {
|
||||
uint8_t backlog[256]{};
|
||||
FakeUART uart;
|
||||
UARTDevice dev(&uart);
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
uart.load(backlog, sizeof(backlog));
|
||||
dev.discard_available();
|
||||
}
|
||||
benchmark::DoNotOptimize(uart.available());
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(UART_DiscardAvailable);
|
||||
|
||||
} // namespace esphome::uart::benchmarks
|
||||
@@ -1,93 +0,0 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/core/frame_reader.h"
|
||||
#include "esphome/core/line_reader.h"
|
||||
|
||||
namespace esphome::benchmarks {
|
||||
|
||||
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
static constexpr char NMEA_GGA[] = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
|
||||
|
||||
// A 30 byte frame with a 4 byte header and a 2 byte tail
|
||||
static constexpr uint8_t FRAME_30[30] = {0xAA, 0xFF, 0x03, 0x00, 0x0E, 0x03, 0xB1, 0x86, 0x10, 0x00,
|
||||
0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xCC};
|
||||
|
||||
// Four A02YYUW distance frames among noise bytes; the doubled 0xFF fails the checksum and resyncs onto the
|
||||
// frame behind it. Ends on a whole frame, so every pass starts clean
|
||||
static constexpr uint8_t ULTRASONIC_NOISY[] = {0x12, 0xFF, 0x07, 0xA1, 0xA7, 0x34, 0xFF, 0xFF, 0x07, 0xA1, 0xA7,
|
||||
0x00, 0xFF, 0x07, 0xA1, 0xA7, 0x56, 0xFF, 0x07, 0xA1, 0xA7};
|
||||
|
||||
// --- LineReader: one GGA sentence fed byte by byte ---
|
||||
|
||||
static void LineReader_NmeaLine(benchmark::State &state) {
|
||||
LineReader<96> reader;
|
||||
const auto *bytes = reinterpret_cast<const uint8_t *>(NMEA_GGA);
|
||||
size_t total = 0;
|
||||
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(bytes);
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
for (size_t j = 0; j < sizeof(NMEA_GGA) - 1; j++) {
|
||||
size_t len;
|
||||
if (reader.feed(bytes[j], len) != nullptr)
|
||||
total += len;
|
||||
}
|
||||
}
|
||||
benchmark::DoNotOptimize(total);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(LineReader_NmeaLine);
|
||||
|
||||
// --- FrameReader: a 30 byte frame behind a 4 byte header ---
|
||||
|
||||
static void FrameReader_30ByteFrame(benchmark::State &state) {
|
||||
FrameReader<sizeof(FRAME_30), 0xAA, 0xFF, 0x03, 0x00> reader;
|
||||
const uint8_t *bytes = FRAME_30;
|
||||
uint32_t frames = 0;
|
||||
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(bytes);
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
for (size_t j = 0; j < sizeof(FRAME_30); j++) {
|
||||
if (reader.feed(bytes[j]) != nullptr)
|
||||
frames++;
|
||||
}
|
||||
}
|
||||
benchmark::DoNotOptimize(frames);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(FrameReader_30ByteFrame);
|
||||
|
||||
// --- FrameReader: 4 byte checksummed frames, skipping noise and resyncing on a bad checksum ---
|
||||
|
||||
static void FrameReader_UltrasonicNoisy(benchmark::State &state) {
|
||||
FrameReader<4, 0xFF> reader;
|
||||
const uint8_t *bytes = ULTRASONIC_NOISY;
|
||||
uint32_t frames = 0;
|
||||
|
||||
for (auto _ : state) {
|
||||
benchmark::DoNotOptimize(bytes);
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
for (size_t j = 0; j < sizeof(ULTRASONIC_NOISY); j++) {
|
||||
const uint8_t *frame = reader.feed(bytes[j]);
|
||||
if (frame == nullptr)
|
||||
continue;
|
||||
if (frame[3] == static_cast<uint8_t>(frame[0] + frame[1] + frame[2])) {
|
||||
frames++;
|
||||
} else {
|
||||
reader.resync();
|
||||
}
|
||||
}
|
||||
}
|
||||
benchmark::DoNotOptimize(frames);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(FrameReader_UltrasonicNoisy);
|
||||
|
||||
} // namespace esphome::benchmarks
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/components/uart/uart_component.h"
|
||||
|
||||
namespace esphome::benchmarks {
|
||||
|
||||
// Serves a fixed byte stream as received data. Every write rewinds it, so the stream answers each request.
|
||||
class FakeUART : public uart::UARTComponent {
|
||||
public:
|
||||
void load(const uint8_t *data, size_t len) {
|
||||
this->data_ = data;
|
||||
this->len_ = len;
|
||||
this->pos_ = 0;
|
||||
}
|
||||
void rewind() { this->pos_ = 0; }
|
||||
|
||||
void write_array(const uint8_t *data, size_t len) override { this->rewind(); }
|
||||
bool peek_byte(uint8_t *data) override {
|
||||
if (this->pos_ >= this->len_)
|
||||
return false;
|
||||
*data = this->data_[this->pos_];
|
||||
return true;
|
||||
}
|
||||
bool read_array(uint8_t *data, size_t len) override {
|
||||
if (len > this->len_ - this->pos_)
|
||||
return false;
|
||||
std::memcpy(data, this->data_ + this->pos_, len);
|
||||
this->pos_ += len;
|
||||
return true;
|
||||
}
|
||||
size_t available() override { return this->len_ - this->pos_; }
|
||||
uart::UARTFlushResult flush() override { return uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS; }
|
||||
void check_logger_conflict() override {}
|
||||
|
||||
protected:
|
||||
const uint8_t *data_{nullptr};
|
||||
size_t len_{0};
|
||||
size_t pos_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::benchmarks
|
||||
Reference in new issue
Block a user