From ce5c36a02af8d848641fed1c78a07fd6ac3d147d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 2 Apr 2026 11:38:18 -1000 Subject: [PATCH] [scheduler] Fix pool imbalance in benchmarks, add static_assert Change kInnerIterations from 2000 to 2100 (divisible by batch sizes 3 and 10) to prevent pool imbalance at iteration boundaries that caused spurious malloc. Add static_assert to each benchmark to catch this at compile time. --- tests/benchmarks/core/bench_scheduler.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/benchmarks/core/bench_scheduler.cpp b/tests/benchmarks/core/bench_scheduler.cpp index b9ef8d054a..155c7949bb 100644 --- a/tests/benchmarks/core/bench_scheduler.cpp +++ b/tests/benchmarks/core/bench_scheduler.cpp @@ -8,7 +8,9 @@ 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; +// Must be divisible by all batch sizes used below (3, 10) to avoid +// pool imbalance at iteration boundaries that causes spurious malloc. +static constexpr int kInnerIterations = 2100; // Warm the scheduler pool by registering and replacing items twice. // The first batch allocates fresh items; the second batch cancels them and @@ -102,6 +104,7 @@ static void Scheduler_SetTimeout(benchmark::State &state) { // components schedule in the same loop iteration. Keeps item count within // the recycling pool (MAX_POOL_SIZE=5) to avoid spurious malloc/free. static constexpr int kBatchSize = 3; + static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize"); warm_pool(scheduler, &dummy_component, kBatchSize, 1000); for (auto _ : state) { uint32_t now = millis(); @@ -128,6 +131,7 @@ static void Scheduler_SetInterval(benchmark::State &state) { // components schedule in the same loop iteration. Keeps item count within // the recycling pool (MAX_POOL_SIZE=5) to avoid spurious malloc/free. static constexpr int kBatchSize = 3; + static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize"); warm_pool(scheduler, &dummy_component, kBatchSize, 1000); for (auto _ : state) { uint32_t now = millis(); @@ -156,6 +160,7 @@ static void Scheduler_Defer(benchmark::State &state) { // components defer in the same loop iteration. Keeps item count within // the recycling pool (MAX_POOL_SIZE=5) to avoid spurious malloc/free. static constexpr int kBatchSize = 3; + static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize"); warm_pool(scheduler, &dummy_component, kBatchSize, 0); for (auto _ : state) { uint32_t now = millis(); @@ -182,6 +187,7 @@ static void Scheduler_SetTimeout_ExceedPool(benchmark::State &state) { // the performance cliff when the recycling pool is exhausted and items // must be malloc'd/freed. static constexpr int kBatchSize = 10; + static_assert(kInnerIterations % kBatchSize == 0, "kInnerIterations must be divisible by kBatchSize"); warm_pool(scheduler, &dummy_component, kBatchSize, 1000); for (auto _ : state) { uint32_t now = millis();