From d7594db6fdc69a0a96598b2de5d5ffd6d3ab9ed5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2026 11:38:17 -0500 Subject: [PATCH] [core] Factor trim_freelist() vector shrink into out-of-line helper The three swap-with-copy lines in trim_freelist() inlined the construct-swap-destruct dance per call site, costing ~440 B flash on ESP32. Move the swap-shrink into a noinline private static helper so all three callers share one body. Net flash cost of vector shrinking drops to ~296 B (from ~444 B) while preserving the same RAM-reclaim behaviour. shrink_to_fit() is a non-binding hint that the toolchain ignores, so the swap-with-copy idiom is still required. --- esphome/core/scheduler.cpp | 19 +++++++++++++------ esphome/core/scheduler.h | 4 ++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 2cf6644c3e..ccbdf3dc05 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -899,6 +899,14 @@ void Scheduler::recycle_item_main_loop_(SchedulerItem *item) { #endif } +// Shrink a SchedulerItem* vector's capacity to its current size via the swap-with-copy +// idiom (std::vector::shrink_to_fit() is non-binding and our toolchain ignores it). +// Out-of-line + noinline so the callers in trim_freelist() share one body instead of +// inlining the construct-swap-destruct dance per call site (~440 B flash on ESP32). +void __attribute__((noinline)) Scheduler::shrink_scheduler_vector_(std::vector *v) { + std::vector(*v).swap(*v); +} + void Scheduler::trim_freelist() { LockGuard guard{this->lock_}; SchedulerItem *item = this->scheduler_item_pool_head_; @@ -915,13 +923,12 @@ void Scheduler::trim_freelist() { this->scheduler_item_pool_head_ = nullptr; this->scheduler_item_pool_size_ = 0; - // The vectors that back items_/to_add_/defer_queue_ also retain their boot-peak - // capacity (std::vector grows by doubling). Swap each with a same-content copy to - // reclaim the slack -- the new vector is sized exactly to its current contents. - std::vector(this->items_).swap(this->items_); - std::vector(this->to_add_).swap(this->to_add_); + // items_/to_add_/defer_queue_ retain their boot-peak vector capacity (vector grows + // by doubling and otherwise keeps the peak). Reclaim that slack as well. + shrink_scheduler_vector_(&this->items_); + shrink_scheduler_vector_(&this->to_add_); #ifndef ESPHOME_THREAD_SINGLE - std::vector(this->defer_queue_).swap(this->defer_queue_); + shrink_scheduler_vector_(&this->defer_queue_); #endif #ifdef ESPHOME_DEBUG_SCHEDULER diff --git a/esphome/core/scheduler.h b/esphome/core/scheduler.h index 7cdb29aae0..ac8ef724c2 100644 --- a/esphome/core/scheduler.h +++ b/esphome/core/scheduler.h @@ -365,6 +365,10 @@ class Scheduler { SchedulerItem *get_item_from_pool_locked_(); private: + // Out-of-line helper that shrinks a SchedulerItem* vector's capacity to its current + // size. Centralised so trim_freelist() doesn't pay flash cost per call site. + static void shrink_scheduler_vector_(std::vector *v); + // Helper to cancel matching items - must be called with lock held. // When find_first=true, stops after the first match (used by set_timer_common_ where // the cancel-before-add invariant guarantees at most one match).