mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Optimize scheduler timing by reducing millis() calls
This commit is contained in:
		@@ -68,7 +68,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
 | 
			
		||||
@@ -94,11 +94,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);
 | 
			
		||||
 | 
			
		||||
@@ -149,7 +149,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", backoff_increase_factor);
 | 
			
		||||
    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 {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user