mirror of
https://github.com/esphome/esphome.git
synced 2026-09-04 03:56:04 +00:00
[scheduler] Fix unrealistic scheduler benchmarks missing periodic drain
Scheduler registration benchmarks (SetTimeout, SetInterval, Defer) were not calling scheduler.call() periodically to drain and clean up cancelled items. In production, call() runs every loop iteration, keeping the scheduler containers small. Without draining, cancelled items accumulated causing O(n²) scan cost in cancel_item_locked_ that doesn't reflect real-world behavior. - SetTimeout: was only calling process_to_add() (no cleanup), now calls call() every kKeyCount iterations - SetInterval: was calling process_to_add() (no cleanup of items_), now calls call() for proper cleanup - Defer: was never draining the defer queue, now calls call() to process deferred items as production does - All three now advance time (++now) to match production loop behavior
This commit is contained in:
@@ -82,12 +82,24 @@ BENCHMARK(Scheduler_Call_5IntervalsFiring);
|
||||
static void Scheduler_SetTimeout(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
// Number of distinct timeout keys; controls how many unique timers exist
|
||||
// simultaneously and the drain cadence for process_to_add().
|
||||
static constexpr int kKeyCount = 5;
|
||||
|
||||
for (auto _ : state) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % 5), 1000, []() {});
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % kKeyCount), 1000, []() {});
|
||||
// Drain periodically to reflect production behavior where call() runs
|
||||
// each main loop iteration. call() moves to_add_ into items_ and cleans
|
||||
// up cancelled items. Without this, cancelled items accumulate causing
|
||||
// O(n²) scan cost in cancel_item_locked_.
|
||||
if ((i + 1) % kKeyCount == 0) {
|
||||
scheduler.call(++now);
|
||||
}
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
// Final drain in case kInnerIterations is not a multiple of kKeyCount
|
||||
scheduler.call(++now);
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
@@ -104,17 +116,19 @@ static void Scheduler_SetInterval(benchmark::State &state) {
|
||||
static constexpr int kKeyCount = 5;
|
||||
|
||||
for (auto _ : state) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_interval(&dummy_component, static_cast<uint32_t>(i % kKeyCount), 1000, []() {});
|
||||
// Drain to_add_ periodically to reflect production behavior where
|
||||
// process_to_add() runs each main loop iteration. Without this,
|
||||
// cancelled items accumulate in to_add_ causing O(n²) scan cost.
|
||||
// Drain periodically to reflect production behavior where call() runs
|
||||
// each main loop iteration. call() moves to_add_ into items_ and cleans
|
||||
// up cancelled items. Without this, cancelled items accumulate causing
|
||||
// O(n²) scan cost in cancel_item_locked_.
|
||||
if ((i + 1) % kKeyCount == 0) {
|
||||
scheduler.process_to_add();
|
||||
scheduler.call(++now);
|
||||
}
|
||||
}
|
||||
// Final drain in case kInnerIterations is not a multiple of 5
|
||||
scheduler.process_to_add();
|
||||
// Final drain in case kInnerIterations is not a multiple of kKeyCount
|
||||
scheduler.call(++now);
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
@@ -126,14 +140,24 @@ BENCHMARK(Scheduler_SetInterval);
|
||||
static void Scheduler_Defer(benchmark::State &state) {
|
||||
Scheduler scheduler;
|
||||
Component dummy_component;
|
||||
// Number of distinct defer keys; controls how many unique defers exist
|
||||
// simultaneously and the drain cadence for call().
|
||||
static constexpr int kKeyCount = 5;
|
||||
|
||||
// defer() is Component::defer which calls set_timeout(delay=0).
|
||||
// Call set_timeout directly since defer() is protected.
|
||||
// Drain with call() periodically to reflect production behavior where
|
||||
// call() runs each main loop iteration, keeping the defer queue small.
|
||||
for (auto _ : state) {
|
||||
uint32_t now = millis();
|
||||
for (int i = 0; i < kInnerIterations; i++) {
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % 5), 0, []() {});
|
||||
scheduler.set_timeout(&dummy_component, static_cast<uint32_t>(i % kKeyCount), 0, []() {});
|
||||
if ((i + 1) % kKeyCount == 0) {
|
||||
scheduler.call(++now);
|
||||
}
|
||||
}
|
||||
scheduler.process_to_add();
|
||||
// Final drain in case kInnerIterations is not a multiple of kKeyCount
|
||||
scheduler.call(++now);
|
||||
benchmark::DoNotOptimize(scheduler);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||
|
||||
Reference in New Issue
Block a user