mirror of
https://github.com/m5stack/esphome.git
synced 2026-05-20 11:52:52 -07:00
254 lines
8.9 KiB
C++
254 lines
8.9 KiB
C++
#include <benchmark/benchmark.h>
|
|
|
|
#include "esphome/components/light/light_output.h"
|
|
#include "esphome/components/light/light_state.h"
|
|
|
|
// Gamma 2.8 forward LUT generated by the light component's Python codegen
|
|
// (see tests/benchmarks/components/light/__init__.py which calls generate_gamma_table())
|
|
extern const uint16_t bench_gamma_2_8_fwd[256];
|
|
|
|
namespace esphome::benchmarks {
|
|
|
|
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
|
static constexpr int kInnerIterations = 2000;
|
|
|
|
// Minimal LightOutput for benchmarking — no real hardware interaction.
|
|
class BenchLightOutput : public light::LightOutput {
|
|
public:
|
|
light::LightTraits get_traits() override { return this->traits_; }
|
|
void write_state(light::LightState * /*state*/) override {}
|
|
|
|
light::LightTraits traits_;
|
|
};
|
|
|
|
// Test subclass to access protected configure_entity_() for benchmark setup.
|
|
class TestLightState : public light::LightState {
|
|
public:
|
|
using LightState::LightState;
|
|
void configure(const char *name) { this->configure_entity_(name, 0x12345678, 0); }
|
|
};
|
|
|
|
// Helper to create a configured RGBWW light state for benchmarks.
|
|
// Note: setup() is not called (no preferences backend), so save_remote_values_()
|
|
// is effectively a no-op. This benchmarks the call/validation path, not persistence.
|
|
static void setup_rgbww_light(BenchLightOutput &output, TestLightState &light) {
|
|
output.traits_.set_supported_color_modes({light::ColorMode::RGB_COLD_WARM_WHITE});
|
|
output.traits_.set_min_mireds(153.0f);
|
|
output.traits_.set_max_mireds(500.0f);
|
|
light.configure("test_light");
|
|
light.set_default_transition_length(0);
|
|
light.set_gamma_correct(2.8f);
|
|
light.set_gamma_table(bench_gamma_2_8_fwd);
|
|
light.set_restore_mode(light::LIGHT_ALWAYS_OFF);
|
|
}
|
|
|
|
// --- LightCall::perform() with instant RGB color change (Home Assistant API path) ---
|
|
// Measures the full call path: validation, set_immediately_, publish, and save.
|
|
// HA sends color_mode explicitly since API 1.6.
|
|
|
|
static void LightCall_RGBInstant(benchmark::State &state) {
|
|
BenchLightOutput output;
|
|
TestLightState light(&output);
|
|
setup_rgbww_light(output, light);
|
|
|
|
// Turn on first so subsequent calls are color changes
|
|
light.make_call().set_state(true).set_brightness(1.0f).set_color_brightness(1.0f).set_transition_length(0).perform();
|
|
|
|
for (auto _ : state) {
|
|
for (int i = 0; i < kInnerIterations; i++) {
|
|
float v = static_cast<float>(i % 256) / 255.0f;
|
|
light.make_call()
|
|
.set_color_mode(light::ColorMode::RGB_COLD_WARM_WHITE)
|
|
.set_red(v)
|
|
.set_green(1.0f - v)
|
|
.set_blue(v * 0.5f)
|
|
.set_transition_length(0)
|
|
.perform();
|
|
}
|
|
benchmark::DoNotOptimize(light.remote_values);
|
|
}
|
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
|
}
|
|
BENCHMARK(LightCall_RGBInstant);
|
|
|
|
// --- LightCall::perform() turn on/off cycle (Home Assistant API path) ---
|
|
// HA sends color_mode explicitly since API 1.6, skipping compute_color_mode_().
|
|
|
|
static void LightCall_ToggleOnOff(benchmark::State &state) {
|
|
BenchLightOutput output;
|
|
TestLightState light(&output);
|
|
setup_rgbww_light(output, light);
|
|
|
|
for (auto _ : state) {
|
|
for (int i = 0; i < kInnerIterations; i++) {
|
|
light.make_call()
|
|
.set_state(i % 2 == 0)
|
|
.set_color_mode(light::ColorMode::RGB_COLD_WARM_WHITE)
|
|
.set_transition_length(0)
|
|
.perform();
|
|
}
|
|
benchmark::DoNotOptimize(light.remote_values);
|
|
}
|
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
|
}
|
|
BENCHMARK(LightCall_ToggleOnOff);
|
|
|
|
// --- LightCall::perform() turn on/off via MQTT ---
|
|
// MQTT never sends color_mode, so compute_color_mode_() runs every call.
|
|
|
|
static void LightCall_ToggleOnOff_MQTT(benchmark::State &state) {
|
|
BenchLightOutput output;
|
|
TestLightState light(&output);
|
|
setup_rgbww_light(output, light);
|
|
|
|
for (auto _ : state) {
|
|
for (int i = 0; i < kInnerIterations; i++) {
|
|
light.make_call().set_state(i % 2 == 0).set_transition_length(0).perform();
|
|
}
|
|
benchmark::DoNotOptimize(light.remote_values);
|
|
}
|
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
|
}
|
|
BENCHMARK(LightCall_ToggleOnOff_MQTT);
|
|
|
|
// --- LightCall::perform() with color temperature via MQTT ---
|
|
// Exercises the transform_parameters_() path that converts color_temperature
|
|
// to cold/warm white fractions. MQTT never sends color_mode, so this also
|
|
// hits compute_color_mode_() every call. Modern HA avoids this path entirely
|
|
// by converting color temp to CW/WW client-side.
|
|
|
|
static void LightCall_ColorTemperature_MQTT(benchmark::State &state) {
|
|
BenchLightOutput output;
|
|
TestLightState light(&output);
|
|
setup_rgbww_light(output, light);
|
|
|
|
light.make_call().set_state(true).set_brightness(1.0f).set_transition_length(0).perform();
|
|
|
|
for (auto _ : state) {
|
|
for (int i = 0; i < kInnerIterations; i++) {
|
|
// Sweep through color temperature range
|
|
float ct = 153.0f + static_cast<float>(i % 348);
|
|
light.make_call().set_color_temperature(ct).set_transition_length(0).perform();
|
|
}
|
|
benchmark::DoNotOptimize(light.remote_values);
|
|
}
|
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
|
}
|
|
BENCHMARK(LightCall_ColorTemperature_MQTT);
|
|
|
|
// --- LightCall::perform() with 1s transition (Home Assistant API path) ---
|
|
// Exercises start_transition_() which allocates a LightTransformer.
|
|
// This is the default HA path when transition_length > 0.
|
|
|
|
static void LightCall_Transition(benchmark::State &state) {
|
|
BenchLightOutput output;
|
|
TestLightState light(&output);
|
|
setup_rgbww_light(output, light);
|
|
|
|
light.make_call().set_state(true).set_brightness(1.0f).set_transition_length(0).perform();
|
|
|
|
for (auto _ : state) {
|
|
for (int i = 0; i < kInnerIterations; i++) {
|
|
float v = static_cast<float>(i % 256) / 255.0f;
|
|
light.make_call()
|
|
.set_color_mode(light::ColorMode::RGB_COLD_WARM_WHITE)
|
|
.set_red(v)
|
|
.set_green(1.0f - v)
|
|
.set_blue(v * 0.5f)
|
|
.set_transition_length(1000)
|
|
.perform();
|
|
}
|
|
benchmark::DoNotOptimize(light.remote_values);
|
|
}
|
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
|
}
|
|
BENCHMARK(LightCall_Transition);
|
|
|
|
// --- LightCall::perform() with cold/warm white (Home Assistant API path) ---
|
|
// Mirrors what modern HA sends: explicit color_mode with direct cold_white
|
|
// and warm_white values. HA converts color temp to CW/WW client-side for
|
|
// CWWW lights (API >= 1.6), so this is the primary HA path.
|
|
|
|
static void LightCall_ColdWarmWhite(benchmark::State &state) {
|
|
BenchLightOutput output;
|
|
TestLightState light(&output);
|
|
setup_rgbww_light(output, light);
|
|
|
|
light.make_call().set_state(true).set_brightness(1.0f).set_transition_length(0).perform();
|
|
|
|
for (auto _ : state) {
|
|
for (int i = 0; i < kInnerIterations; i++) {
|
|
float frac = static_cast<float>(i % 256) / 255.0f;
|
|
light.make_call()
|
|
.set_color_mode(light::ColorMode::RGB_COLD_WARM_WHITE)
|
|
.set_cold_white(1.0f - frac)
|
|
.set_warm_white(frac)
|
|
.set_transition_length(0)
|
|
.perform();
|
|
}
|
|
benchmark::DoNotOptimize(light.remote_values);
|
|
}
|
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
|
}
|
|
BENCHMARK(LightCall_ColdWarmWhite);
|
|
|
|
// --- LightState::publish_state() with a remote values listener ---
|
|
// Measures listener notification overhead.
|
|
|
|
static void LightPublish_WithListener(benchmark::State &state) {
|
|
BenchLightOutput output;
|
|
TestLightState light(&output);
|
|
setup_rgbww_light(output, light);
|
|
|
|
struct TestListener : public light::LightRemoteValuesListener {
|
|
void on_light_remote_values_update() override { count_++; }
|
|
uint64_t count_{0};
|
|
} listener;
|
|
light.add_remote_values_listener(&listener);
|
|
|
|
for (auto _ : state) {
|
|
for (int i = 0; i < kInnerIterations; i++) {
|
|
light.publish_state();
|
|
}
|
|
benchmark::DoNotOptimize(listener.count_);
|
|
}
|
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
|
}
|
|
BENCHMARK(LightPublish_WithListener);
|
|
|
|
// --- current_values_as_rgbww output conversion with gamma LUT ---
|
|
// Measures the output conversion path that real light drivers call
|
|
// from write_state() to get hardware PWM values, including gamma
|
|
// table lookups via the LUT generated by Python codegen.
|
|
|
|
static void LightOutput_RGBWW(benchmark::State &state) {
|
|
BenchLightOutput output;
|
|
TestLightState light(&output);
|
|
setup_rgbww_light(output, light);
|
|
|
|
light.make_call()
|
|
.set_state(true)
|
|
.set_brightness(0.8f)
|
|
.set_color_brightness(0.6f)
|
|
.set_red(1.0f)
|
|
.set_green(0.5f)
|
|
.set_blue(0.2f)
|
|
.set_cold_white(0.7f)
|
|
.set_warm_white(0.3f)
|
|
.set_transition_length(0)
|
|
.perform();
|
|
|
|
float r, g, b, cw, ww;
|
|
for (auto _ : state) {
|
|
for (int i = 0; i < kInnerIterations; i++) {
|
|
light.current_values_as_rgbww(&r, &g, &b, &cw, &ww);
|
|
}
|
|
benchmark::DoNotOptimize(r);
|
|
benchmark::DoNotOptimize(cw);
|
|
}
|
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
|
}
|
|
BENCHMARK(LightOutput_RGBWW);
|
|
|
|
} // namespace esphome::benchmarks
|