mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:43:00 +00:00
[core] Add CodSpeed C++ benchmarks for protobuf, main loop, and helpers (#14878)
This commit is contained in:
22
tests/benchmarks/core/bench_application_loop.cpp
Normal file
22
tests/benchmarks/core/bench_application_loop.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome::benchmarks {
|
||||
|
||||
// Benchmark Application::loop() with no registered components.
|
||||
// App is initialized by original_setup() in main.cpp (code-generated
|
||||
// pre_setup, area/device registration, looping_components_.init).
|
||||
// This measures the baseline overhead of the main loop: scheduler,
|
||||
// timing, before/after loop tasks, and yield_with_select_.
|
||||
static void ApplicationLoop_Empty(benchmark::State &state) {
|
||||
// Set loop interval to 0 so yield_with_select_ returns immediately
|
||||
// instead of sleeping. This benchmarks the loop overhead, not the sleep.
|
||||
App.set_loop_interval(0);
|
||||
for (auto _ : state) {
|
||||
App.loop();
|
||||
}
|
||||
}
|
||||
BENCHMARK(ApplicationLoop_Empty);
|
||||
|
||||
} // namespace esphome::benchmarks
|
||||
41
tests/benchmarks/core/bench_helpers.cpp
Normal file
41
tests/benchmarks/core/bench_helpers.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::benchmarks {
|
||||
|
||||
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
||||
// Without this, the ~60ns per-iteration valgrind start/stop cost dominates
|
||||
// sub-microsecond benchmarks.
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
// --- random_float() ---
|
||||
// Ported from ol.yaml:148 "Random Float Benchmark"
|
||||
|
||||
static void RandomFloat(benchmark::State &state) {
|
||||
for (auto _ : state) {
|
||||
float result = 0.0f;
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
result += random_float();
|
||||
}
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(RandomFloat);
|
||||
|
||||
// --- random_uint32() ---
|
||||
|
||||
static void RandomUint32(benchmark::State &state) {
|
||||
for (auto _ : state) {
|
||||
uint32_t result = 0;
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
result += random_uint32();
|
||||
}
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(RandomUint32);
|
||||
|
||||
} // namespace esphome::benchmarks
|
||||
54
tests/benchmarks/core/bench_logger.cpp
Normal file
54
tests/benchmarks/core/bench_logger.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::benchmarks {
|
||||
|
||||
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
||||
// Without this, the ~60ns per-iteration valgrind start/stop cost dominates
|
||||
// sub-microsecond benchmarks.
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
static const char *const TAG = "bench";
|
||||
|
||||
// --- Log a message with no format specifiers (fastest path) ---
|
||||
|
||||
static void Logger_NoFormat(benchmark::State &state) {
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
ESP_LOGW(TAG, "Something happened");
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Logger_NoFormat);
|
||||
|
||||
// --- Log a message with 3 uint32_t format specifiers ---
|
||||
|
||||
static void Logger_3Uint32(benchmark::State &state) {
|
||||
uint32_t a = 12345, b = 67890, c = 99999;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
ESP_LOGW(TAG, "Values: %" PRIu32 " %" PRIu32 " %" PRIu32, a, b, c);
|
||||
}
|
||||
benchmark::DoNotOptimize(a);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Logger_3Uint32);
|
||||
|
||||
// --- Log a message with 3 floats (common for sensor values) ---
|
||||
|
||||
static void Logger_3Float(benchmark::State &state) {
|
||||
float temp = 23.456f, humidity = 67.89f, pressure = 1013.25f;
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
ESP_LOGW(TAG, "Sensor: %.2f %.1f %.2f", temp, humidity, pressure);
|
||||
}
|
||||
benchmark::DoNotOptimize(temp);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Logger_3Float);
|
||||
|
||||
} // namespace esphome::benchmarks
|
||||
133
tests/benchmarks/core/bench_scheduler.cpp
Normal file
133
tests/benchmarks/core/bench_scheduler.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "esphome/core/scheduler.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome::benchmarks {
|
||||
|
||||
// Inner iteration count to amortize CodSpeed instrumentation overhead.
|
||||
// Without this, the ~60ns per-iteration valgrind start/stop cost dominates
|
||||
// sub-microsecond benchmarks.
|
||||
static constexpr int kInnerIterations = 2000;
|
||||
|
||||
// --- Scheduler fast path: no work to do ---
|
||||
|
||||
static void Scheduler_Call_NoWork(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
uint32_t now = millis();
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.call(now);
|
||||
}
|
||||
benchmark::DoNotOptimize(now);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Scheduler_Call_NoWork);
|
||||
|
||||
// --- Scheduler with timers: call() when timers exist but aren't due ---
|
||||
|
||||
static void Scheduler_Call_TimersNotDue(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
|
||||
// Add some timeouts far in the future
|
||||
for (int i = 0; i < 10; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i), 1000000, []() {});
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
|
||||
uint32_t now = millis();
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.call(now);
|
||||
}
|
||||
benchmark::DoNotOptimize(now);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Scheduler_Call_TimersNotDue);
|
||||
|
||||
// --- Scheduler with 5 intervals firing every call ---
|
||||
|
||||
static void Scheduler_Call_5IntervalsFiring(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
int fire_count = 0;
|
||||
|
||||
// Benchmarks the heap-based scheduler dispatch with 5 callbacks firing.
|
||||
// Uses monotonically increasing fake time so intervals reliably fire every call.
|
||||
// USE_BENCHMARK ifdef in component.h disables WarnIfComponentBlockingGuard
|
||||
// (fake now > real millis() would cause underflow in finish()).
|
||||
// interval=0 would cause an infinite loop (reschedules at same now).
|
||||
for (int i = 0; i < 5; i++) {
|
||||
scheduler.set_interval(&dummy_component, static_cast<uint32_t>(i), 1, [&fire_count]() { fire_count++; });
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
|
||||
uint32_t now = millis() + 100;
|
||||
|
||||
for (auto _ : state) {
|
||||
scheduler.call(now);
|
||||
now++;
|
||||
benchmark::DoNotOptimize(fire_count);
|
||||
}
|
||||
}
|
||||
BENCHMARK(Scheduler_Call_5IntervalsFiring);
|
||||
|
||||
// --- Scheduler: set_timeout registration ---
|
||||
|
||||
static void Scheduler_SetTimeout(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % 5), 1000, []() {});
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Scheduler_SetTimeout);
|
||||
|
||||
// --- Scheduler: set_interval registration ---
|
||||
|
||||
static void Scheduler_SetInterval(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_interval(&dummy_component, static_cast<uint32_t>(i % 5), 1000, []() {});
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Scheduler_SetInterval);
|
||||
|
||||
// --- Scheduler: defer registration (set_timeout with delay=0) ---
|
||||
|
||||
static void Scheduler_Defer(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
|
||||
// defer() is Component::defer which calls set_timeout(delay=0).
|
||||
// Call set_timeout directly since defer() is protected.
|
||||
for (auto _ : state) {
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % 5), 0, []() {});
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
}
|
||||
BENCHMARK(Scheduler_Defer);
|
||||
|
||||
} // namespace esphome::benchmarks
|
||||
Reference in New Issue
Block a user