From f8191410e375974d1eb47b53e9329c307c937ff2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 14 Nov 2025 20:05:43 -0600 Subject: [PATCH 1/2] [core] Optimize DelayAction for no-argument case using if constexpr --- esphome/core/base_automation.h | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index a5e6139182..4a7f550b22 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -178,7 +178,6 @@ template class DelayAction : public Action, public Compon TEMPLATABLE_VALUE(uint32_t, delay) void play_complex(const Ts &...x) override { - auto f = std::bind(&DelayAction::play_next_, this, x...); this->num_running_++; // If num_running_ > 1, we have multiple instances running in parallel @@ -187,9 +186,27 @@ template class DelayAction : public Action, public Compon // WARNING: This can accumulate delays if scripts are triggered faster than they complete! // Users should set max_runs on parallel scripts to limit concurrent executions. // Issue #10264: This is a workaround for parallel script delays interfering with each other. - App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, - /* is_static_string= */ true, "delay", this->delay_.value(x...), std::move(f), - /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); + + // Optimization: For no-argument delays (most common case), use direct lambda + // instead of std::bind to avoid bind overhead (~16 bytes heap + faster execution) + if constexpr (sizeof...(Ts) == 0) { + App.scheduler.set_timer_common_( + this, Scheduler::SchedulerItem::TIMEOUT, + /* is_static_string= */ true, "delay", this->delay_.value(), + [this]() { + if (this->num_running_ > 0) { + this->play_next_(); + } + }, + /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); + } else { + // For delays with arguments, use std::bind to preserve argument values + // Arguments must be copied because original references may be invalid after delay + auto f = std::bind(&DelayAction::play_next_, this, x...); + App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, + /* is_static_string= */ true, "delay", this->delay_.value(x...), std::move(f), + /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); + } } float get_setup_priority() const override { return setup_priority::HARDWARE; } From f1bc3c68ddf7ae8e83dd301db39434af234a88c5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 14 Nov 2025 20:09:38 -0600 Subject: [PATCH 2/2] [core] Optimize DelayAction for no-argument case using if constexpr --- esphome/core/base_automation.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 4a7f550b22..c2519da839 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -192,12 +192,7 @@ template class DelayAction : public Action, public Compon if constexpr (sizeof...(Ts) == 0) { App.scheduler.set_timer_common_( this, Scheduler::SchedulerItem::TIMEOUT, - /* is_static_string= */ true, "delay", this->delay_.value(), - [this]() { - if (this->num_running_ > 0) { - this->play_next_(); - } - }, + /* is_static_string= */ true, "delay", this->delay_.value(), [this]() { this->play_next_(); }, /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1); } else { // For delays with arguments, use std::bind to preserve argument values