Files
esphome/tests/benchmarks/core/bench_scheduler.cpp
T
J. Nick Koston ed539e17ff Add scheduler benchmark with 5 intervals firing per call
Adds Scheduler_Call_5IntervalsFiring: 5 intervals with 1ms period,
time advancing each inner iteration so all 5 fire every call().
This benchmarks the real scheduler hot path where callbacks execute.
2026-03-16 23:57:39 -10:00

107 lines
3.0 KiB
C++

#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;
// Add 5 intervals with 1ms period — they fire every call when time advances
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();
// Start at a known time so intervals are immediately due
uint32_t now = millis() + 100;
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
scheduler.call(now);
// Advance time by 1ms so intervals are due again next call
now++;
}
benchmark::DoNotOptimize(fire_count);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Scheduler_Call_5IntervalsFiring);
// --- Scheduler: next_schedule_in() calculation ---
static void Scheduler_NextScheduleIn(benchmark::State &state) {
Scheduler scheduler;
Component dummy_component;
// Add some timeouts
for (int i = 0; i < 10; i++) {
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i), 1000 * (i + 1), []() {});
}
scheduler.process_to_add();
uint32_t now = millis();
for (auto _ : state) {
optional<uint32_t> result;
for (int i = 0; i < kInnerIterations; i++) {
result = scheduler.next_schedule_in(now);
}
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(Scheduler_NextScheduleIn);
} // namespace esphome::benchmarks