From 8010386614fac30a703dd5b79707095f61f81de4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Mar 2026 21:17:22 -1000 Subject: [PATCH] [scheduler] Use placement-new for std::function move in set_timer_common_ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC's std::function::operator=(function&&) implements move-assignment as a swap dance: construct temporary, swap with *this, destroy temporary. This generates two std::swap<_Any_data> calls plus a destructor even when the target is known to be empty. Since scheduler items returned from the pool or freshly allocated always have an empty callback, we can use explicit destroy + placement move-construct to bypass the swap overhead. Measured savings in set_timer_common_: - ESP8266 (Xtensa LX106): 473 → 421 bytes (-52 B, -11%) - ESP32-S3 (Xtensa LX7): 412 → 376 bytes (-36 B, -9%) --- esphome/core/scheduler.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 63e1006b03..034206c8f6 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -164,7 +164,13 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type item->component = component; item->set_name(name_type, static_name, hash_or_id); item->type = type; - item->callback = std::move(func); + // Use destroy + placement-new instead of move-assignment. + // GCC's std::function::operator=(function&&) does a full swap dance even when the + // target is empty. Since recycled/new items always have an empty callback, we can + // destroy the empty one (no-op) and move-construct directly, saving ~40 bytes of + // swap/destructor code on Xtensa. + item->callback.~function(); + new (&item->callback) std::function(std::move(func)); // Reset remove flag - recycled items may have been cancelled (remove=true) in previous use this->set_item_removed_(item, false); item->is_retry = is_retry;