mirror of
https://github.com/esphome/esphome.git
synced 2025-07-29 06:36:45 +00:00
Optimize scheduler timing by reducing millis() calls (#9524)
This commit is contained in:
parent
b695f13f86
commit
b1c86fe30e
@ -71,7 +71,7 @@ void Application::setup() {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
uint8_t new_app_state = STATUS_LED_WARNING;
|
uint8_t new_app_state = STATUS_LED_WARNING;
|
||||||
this->scheduler.call();
|
this->scheduler.call(millis());
|
||||||
this->feed_wdt();
|
this->feed_wdt();
|
||||||
for (uint32_t j = 0; j <= i; j++) {
|
for (uint32_t j = 0; j <= i; j++) {
|
||||||
// Update loop_component_start_time_ right before calling each component
|
// Update loop_component_start_time_ right before calling each component
|
||||||
@ -97,11 +97,11 @@ void Application::setup() {
|
|||||||
void Application::loop() {
|
void Application::loop() {
|
||||||
uint8_t new_app_state = 0;
|
uint8_t new_app_state = 0;
|
||||||
|
|
||||||
this->scheduler.call();
|
|
||||||
|
|
||||||
// Get the initial loop time at the start
|
// Get the initial loop time at the start
|
||||||
uint32_t last_op_end_time = millis();
|
uint32_t last_op_end_time = millis();
|
||||||
|
|
||||||
|
this->scheduler.call(last_op_end_time);
|
||||||
|
|
||||||
// Feed WDT with time
|
// Feed WDT with time
|
||||||
this->feed_wdt(last_op_end_time);
|
this->feed_wdt(last_op_end_time);
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ void Application::loop() {
|
|||||||
this->yield_with_select_(0);
|
this->yield_with_select_(0);
|
||||||
} else {
|
} else {
|
||||||
uint32_t delay_time = this->loop_interval_ - elapsed;
|
uint32_t delay_time = this->loop_interval_ - elapsed;
|
||||||
uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time);
|
uint32_t next_schedule = this->scheduler.next_schedule_in(last_op_end_time).value_or(delay_time);
|
||||||
// next_schedule is max 0.5*delay_time
|
// next_schedule is max 0.5*delay_time
|
||||||
// otherwise interval=0 schedules result in constant looping with almost no sleep
|
// otherwise interval=0 schedules result in constant looping with almost no sleep
|
||||||
next_schedule = std::max(next_schedule, delay_time / 2);
|
next_schedule = std::max(next_schedule, delay_time / 2);
|
||||||
|
@ -91,7 +91,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto now = this->millis_();
|
const auto now = this->millis_64_(millis());
|
||||||
|
|
||||||
// Type-specific setup
|
// Type-specific setup
|
||||||
if (type == SchedulerItem::INTERVAL) {
|
if (type == SchedulerItem::INTERVAL) {
|
||||||
@ -193,9 +193,7 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin
|
|||||||
name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor);
|
name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor);
|
||||||
|
|
||||||
if (backoff_increase_factor < 0.0001) {
|
if (backoff_increase_factor < 0.0001) {
|
||||||
ESP_LOGE(TAG,
|
ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name.c_str());
|
||||||
"set_retry(name='%s'): backoff_factor cannot be close to zero nor negative (%0.1f). Using 1.0 instead",
|
|
||||||
name.c_str(), backoff_increase_factor);
|
|
||||||
backoff_increase_factor = 1;
|
backoff_increase_factor = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,19 +213,19 @@ bool HOT Scheduler::cancel_retry(Component *component, const std::string &name)
|
|||||||
return this->cancel_timeout(component, "retry$" + name);
|
return this->cancel_timeout(component, "retry$" + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<uint32_t> HOT Scheduler::next_schedule_in() {
|
optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
|
||||||
// IMPORTANT: This method should only be called from the main thread (loop task).
|
// IMPORTANT: This method should only be called from the main thread (loop task).
|
||||||
// It calls empty_() and accesses items_[0] without holding a lock, which is only
|
// It calls empty_() and accesses items_[0] without holding a lock, which is only
|
||||||
// safe when called from the main thread. Other threads must not call this method.
|
// safe when called from the main thread. Other threads must not call this method.
|
||||||
if (this->empty_())
|
if (this->empty_())
|
||||||
return {};
|
return {};
|
||||||
auto &item = this->items_[0];
|
auto &item = this->items_[0];
|
||||||
const auto now = this->millis_();
|
const auto now_64 = this->millis_64_(now);
|
||||||
if (item->next_execution_ < now)
|
if (item->next_execution_ < now_64)
|
||||||
return 0;
|
return 0;
|
||||||
return item->next_execution_ - now;
|
return item->next_execution_ - now_64;
|
||||||
}
|
}
|
||||||
void HOT Scheduler::call() {
|
void HOT Scheduler::call(uint32_t now) {
|
||||||
#if !defined(USE_ESP8266) && !defined(USE_RP2040)
|
#if !defined(USE_ESP8266) && !defined(USE_RP2040)
|
||||||
// Process defer queue first to guarantee FIFO execution order for deferred items.
|
// Process defer queue first to guarantee FIFO execution order for deferred items.
|
||||||
// Previously, defer() used the heap which gave undefined order for equal timestamps,
|
// Previously, defer() used the heap which gave undefined order for equal timestamps,
|
||||||
@ -256,22 +254,22 @@ void HOT Scheduler::call() {
|
|||||||
// Execute callback without holding lock to prevent deadlocks
|
// Execute callback without holding lock to prevent deadlocks
|
||||||
// if the callback tries to call defer() again
|
// if the callback tries to call defer() again
|
||||||
if (!this->should_skip_item_(item.get())) {
|
if (!this->should_skip_item_(item.get())) {
|
||||||
this->execute_item_(item.get());
|
this->execute_item_(item.get(), now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto now = this->millis_();
|
const auto now_64 = this->millis_64_(now);
|
||||||
this->process_to_add();
|
this->process_to_add();
|
||||||
|
|
||||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||||
static uint64_t last_print = 0;
|
static uint64_t last_print = 0;
|
||||||
|
|
||||||
if (now - last_print > 2000) {
|
if (now_64 - last_print > 2000) {
|
||||||
last_print = now;
|
last_print = now_64;
|
||||||
std::vector<std::unique_ptr<SchedulerItem>> old_items;
|
std::vector<std::unique_ptr<SchedulerItem>> old_items;
|
||||||
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now, this->millis_major_,
|
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now_64,
|
||||||
this->last_millis_);
|
this->millis_major_, this->last_millis_);
|
||||||
while (!this->empty_()) {
|
while (!this->empty_()) {
|
||||||
std::unique_ptr<SchedulerItem> item;
|
std::unique_ptr<SchedulerItem> item;
|
||||||
{
|
{
|
||||||
@ -283,7 +281,7 @@ void HOT Scheduler::call() {
|
|||||||
const char *name = item->get_name();
|
const char *name = item->get_name();
|
||||||
ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64,
|
ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64,
|
||||||
item->get_type_str(), item->get_source(), name ? name : "(null)", item->interval,
|
item->get_type_str(), item->get_source(), name ? name : "(null)", item->interval,
|
||||||
item->next_execution_ - now, item->next_execution_);
|
item->next_execution_ - now_64, item->next_execution_);
|
||||||
|
|
||||||
old_items.push_back(std::move(item));
|
old_items.push_back(std::move(item));
|
||||||
}
|
}
|
||||||
@ -328,7 +326,7 @@ void HOT Scheduler::call() {
|
|||||||
{
|
{
|
||||||
// Don't copy-by value yet
|
// Don't copy-by value yet
|
||||||
auto &item = this->items_[0];
|
auto &item = this->items_[0];
|
||||||
if (item->next_execution_ > now) {
|
if (item->next_execution_ > now_64) {
|
||||||
// Not reached timeout yet, done for this call
|
// Not reached timeout yet, done for this call
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -342,13 +340,13 @@ void HOT Scheduler::call() {
|
|||||||
const char *item_name = item->get_name();
|
const char *item_name = item->get_name();
|
||||||
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",
|
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",
|
||||||
item->get_type_str(), item->get_source(), item_name ? item_name : "(null)", item->interval,
|
item->get_type_str(), item->get_source(), item_name ? item_name : "(null)", item->interval,
|
||||||
item->next_execution_, now);
|
item->next_execution_, now_64);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Warning: During callback(), a lot of stuff can happen, including:
|
// Warning: During callback(), a lot of stuff can happen, including:
|
||||||
// - timeouts/intervals get added, potentially invalidating vector pointers
|
// - timeouts/intervals get added, potentially invalidating vector pointers
|
||||||
// - timeouts/intervals get cancelled
|
// - timeouts/intervals get cancelled
|
||||||
this->execute_item_(item.get());
|
this->execute_item_(item.get(), now);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -367,7 +365,7 @@ void HOT Scheduler::call() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (item->type == SchedulerItem::INTERVAL) {
|
if (item->type == SchedulerItem::INTERVAL) {
|
||||||
item->next_execution_ = now + item->interval;
|
item->next_execution_ = now_64 + item->interval;
|
||||||
// Add new item directly to to_add_
|
// Add new item directly to to_add_
|
||||||
// since we have the lock held
|
// since we have the lock held
|
||||||
this->to_add_.push_back(std::move(item));
|
this->to_add_.push_back(std::move(item));
|
||||||
@ -423,11 +421,9 @@ void HOT Scheduler::pop_raw_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper to execute a scheduler item
|
// Helper to execute a scheduler item
|
||||||
void HOT Scheduler::execute_item_(SchedulerItem *item) {
|
void HOT Scheduler::execute_item_(SchedulerItem *item, uint32_t now) {
|
||||||
App.set_current_component(item->component);
|
App.set_current_component(item->component);
|
||||||
|
WarnIfComponentBlockingGuard guard{item->component, now};
|
||||||
uint32_t now_ms = millis();
|
|
||||||
WarnIfComponentBlockingGuard guard{item->component, now_ms};
|
|
||||||
item->callback();
|
item->callback();
|
||||||
guard.finish();
|
guard.finish();
|
||||||
}
|
}
|
||||||
@ -486,15 +482,15 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|||||||
return total_cancelled > 0;
|
return total_cancelled > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Scheduler::millis_() {
|
uint64_t Scheduler::millis_64_(uint32_t now) {
|
||||||
// Get the current 32-bit millis value
|
|
||||||
const uint32_t now = millis();
|
|
||||||
// Check for rollover by comparing with last value
|
// Check for rollover by comparing with last value
|
||||||
if (now < this->last_millis_) {
|
if (now < this->last_millis_) {
|
||||||
// Detected rollover (happens every ~49.7 days)
|
// Detected rollover (happens every ~49.7 days)
|
||||||
this->millis_major_++;
|
this->millis_major_++;
|
||||||
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||||
ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms",
|
ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms",
|
||||||
now + (static_cast<uint64_t>(this->millis_major_) << 32));
|
now + (static_cast<uint64_t>(this->millis_major_) << 32));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
this->last_millis_ = now;
|
this->last_millis_ = now;
|
||||||
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
|
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
|
||||||
|
@ -52,9 +52,9 @@ class Scheduler {
|
|||||||
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
||||||
bool cancel_retry(Component *component, const std::string &name);
|
bool cancel_retry(Component *component, const std::string &name);
|
||||||
|
|
||||||
optional<uint32_t> next_schedule_in();
|
optional<uint32_t> next_schedule_in(uint32_t now);
|
||||||
|
|
||||||
void call();
|
void call(uint32_t now);
|
||||||
|
|
||||||
void process_to_add();
|
void process_to_add();
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ class Scheduler {
|
|||||||
void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr,
|
void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr,
|
||||||
uint32_t delay, std::function<void()> func);
|
uint32_t delay, std::function<void()> func);
|
||||||
|
|
||||||
uint64_t millis_();
|
uint64_t millis_64_(uint32_t now);
|
||||||
void cleanup_();
|
void cleanup_();
|
||||||
void pop_raw_();
|
void pop_raw_();
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ class Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper to execute a scheduler item
|
// Helper to execute a scheduler item
|
||||||
void execute_item_(SchedulerItem *item);
|
void execute_item_(SchedulerItem *item, uint32_t now);
|
||||||
|
|
||||||
// Helper to check if item should be skipped
|
// Helper to check if item should be skipped
|
||||||
bool should_skip_item_(const SchedulerItem *item) const {
|
bool should_skip_item_(const SchedulerItem *item) const {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user