[scheduler] Use placement-new for std::function move in set_timer_common_

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%)
This commit is contained in:
J. Nick Koston
2026-03-12 21:17:22 -10:00
parent 15ec46abfe
commit 8010386614
+7 -1
View File
@@ -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<void()>(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;