mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +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 {
|
||||
uint8_t new_app_state = STATUS_LED_WARNING;
|
||||
this->scheduler.call();
|
||||
this->scheduler.call(millis());
|
||||
this->feed_wdt();
|
||||
for (uint32_t j = 0; j <= i; j++) {
|
||||
// Update loop_component_start_time_ right before calling each component
|
||||
@ -97,11 +97,11 @@ void Application::setup() {
|
||||
void Application::loop() {
|
||||
uint8_t new_app_state = 0;
|
||||
|
||||
this->scheduler.call();
|
||||
|
||||
// Get the initial loop time at the start
|
||||
uint32_t last_op_end_time = millis();
|
||||
|
||||
this->scheduler.call(last_op_end_time);
|
||||
|
||||
// Feed WDT with time
|
||||
this->feed_wdt(last_op_end_time);
|
||||
|
||||
@ -160,7 +160,7 @@ void Application::loop() {
|
||||
this->yield_with_select_(0);
|
||||
} else {
|
||||
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
|
||||
// otherwise interval=0 schedules result in constant looping with almost no sleep
|
||||
next_schedule = std::max(next_schedule, delay_time / 2);
|
||||
|
@ -91,7 +91,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto now = this->millis_();
|
||||
const auto now = this->millis_64_(millis());
|
||||
|
||||
// Type-specific setup
|
||||
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);
|
||||
|
||||
if (backoff_increase_factor < 0.0001) {
|
||||
ESP_LOGE(TAG,
|
||||
"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);
|
||||
ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name.c_str());
|
||||
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);
|
||||
}
|
||||
|
||||
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).
|
||||
// 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.
|
||||
if (this->empty_())
|
||||
return {};
|
||||
auto &item = this->items_[0];
|
||||
const auto now = this->millis_();
|
||||
if (item->next_execution_ < now)
|
||||
const auto now_64 = this->millis_64_(now);
|
||||
if (item->next_execution_ < now_64)
|
||||
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)
|
||||
// Process defer queue first to guarantee FIFO execution order for deferred items.
|
||||
// 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
|
||||
// if the callback tries to call defer() again
|
||||
if (!this->should_skip_item_(item.get())) {
|
||||
this->execute_item_(item.get());
|
||||
this->execute_item_(item.get(), now);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto now = this->millis_();
|
||||
const auto now_64 = this->millis_64_(now);
|
||||
this->process_to_add();
|
||||
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
static uint64_t last_print = 0;
|
||||
|
||||
if (now - last_print > 2000) {
|
||||
last_print = now;
|
||||
if (now_64 - last_print > 2000) {
|
||||
last_print = now_64;
|
||||
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_,
|
||||
this->last_millis_);
|
||||
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now_64,
|
||||
this->millis_major_, this->last_millis_);
|
||||
while (!this->empty_()) {
|
||||
std::unique_ptr<SchedulerItem> item;
|
||||
{
|
||||
@ -283,7 +281,7 @@ void HOT Scheduler::call() {
|
||||
const char *name = item->get_name();
|
||||
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->next_execution_ - now, item->next_execution_);
|
||||
item->next_execution_ - now_64, item->next_execution_);
|
||||
|
||||
old_items.push_back(std::move(item));
|
||||
}
|
||||
@ -328,7 +326,7 @@ void HOT Scheduler::call() {
|
||||
{
|
||||
// Don't copy-by value yet
|
||||
auto &item = this->items_[0];
|
||||
if (item->next_execution_ > now) {
|
||||
if (item->next_execution_ > now_64) {
|
||||
// Not reached timeout yet, done for this call
|
||||
break;
|
||||
}
|
||||
@ -342,13 +340,13 @@ void HOT Scheduler::call() {
|
||||
const char *item_name = item->get_name();
|
||||
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->next_execution_, now);
|
||||
item->next_execution_, now_64);
|
||||
#endif
|
||||
|
||||
// Warning: During callback(), a lot of stuff can happen, including:
|
||||
// - timeouts/intervals get added, potentially invalidating vector pointers
|
||||
// - 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) {
|
||||
item->next_execution_ = now + item->interval;
|
||||
item->next_execution_ = now_64 + item->interval;
|
||||
// Add new item directly to to_add_
|
||||
// since we have the lock held
|
||||
this->to_add_.push_back(std::move(item));
|
||||
@ -423,11 +421,9 @@ void HOT Scheduler::pop_raw_() {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
uint32_t now_ms = millis();
|
||||
WarnIfComponentBlockingGuard guard{item->component, now_ms};
|
||||
WarnIfComponentBlockingGuard guard{item->component, now};
|
||||
item->callback();
|
||||
guard.finish();
|
||||
}
|
||||
@ -486,15 +482,15 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
||||
return total_cancelled > 0;
|
||||
}
|
||||
|
||||
uint64_t Scheduler::millis_() {
|
||||
// Get the current 32-bit millis value
|
||||
const uint32_t now = millis();
|
||||
uint64_t Scheduler::millis_64_(uint32_t now) {
|
||||
// Check for rollover by comparing with last value
|
||||
if (now < this->last_millis_) {
|
||||
// Detected rollover (happens every ~49.7 days)
|
||||
this->millis_major_++;
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms",
|
||||
now + (static_cast<uint64_t>(this->millis_major_) << 32));
|
||||
#endif
|
||||
}
|
||||
this->last_millis_ = now;
|
||||
// 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);
|
||||
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();
|
||||
|
||||
@ -137,7 +137,7 @@ class Scheduler {
|
||||
void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr,
|
||||
uint32_t delay, std::function<void()> func);
|
||||
|
||||
uint64_t millis_();
|
||||
uint64_t millis_64_(uint32_t now);
|
||||
void cleanup_();
|
||||
void pop_raw_();
|
||||
|
||||
@ -175,7 +175,7 @@ class Scheduler {
|
||||
}
|
||||
|
||||
// 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
|
||||
bool should_skip_item_(const SchedulerItem *item) const {
|
||||
|
Loading…
x
Reference in New Issue
Block a user