diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 1be193bb7e..c745aa0ae5 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -340,8 +340,8 @@ void Application::calculate_looping_components_() { } } - // Pre-reserve vector to avoid reallocations - this->looping_components_.reserve(total_looping); + // Initialize FixedVector with exact size - no reallocation possible + this->looping_components_.init(total_looping); // Add all components with loop override that aren't already LOOP_DONE // Some components (like logger) may call disable_loop() during initialization diff --git a/esphome/core/application.h b/esphome/core/application.h index 1f22499051..b0f9c23191 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -472,7 +472,7 @@ class Application { // - When a component is enabled, it's swapped with the first inactive component // and active_end_ is incremented // - This eliminates branch mispredictions from flag checking in the hot loop - std::vector looping_components_{}; + FixedVector looping_components_{}; #ifdef USE_SOCKET_SELECT_SUPPORT std::vector socket_fds_; // Vector of all monitored socket file descriptors #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index e06f2d15ef..864fd92ffc 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -159,6 +159,50 @@ template class StaticVector { const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } }; +/// Fixed-capacity vector - allocates once at runtime, never reallocates +/// This avoids std::vector template overhead (_M_realloc_insert, _M_default_append) +/// when size is known at initialization but not at compile time +template class FixedVector { + private: + T *data_{nullptr}; + size_t size_{0}; + size_t capacity_{0}; + + public: + FixedVector() = default; + + ~FixedVector() { + if (data_ != nullptr) { + delete[] data_; + } + } + + // Disable copy to avoid accidental copies + FixedVector(const FixedVector &) = delete; + FixedVector &operator=(const FixedVector &) = delete; + + // Allocate capacity - can only be called once on empty vector + void init(size_t n) { + if (data_ == nullptr && n > 0) { + data_ = new T[n]; + capacity_ = n; + size_ = 0; + } + } + + // Add element (no reallocation - must have initialized capacity) + void push_back(const T &value) { + if (size_ < capacity_) { + data_[size_++] = value; + } + } + + size_t size() const { return size_; } + + T &operator[](size_t i) { return data_[i]; } + const T &operator[](size_t i) const { return data_[i]; } +}; + ///@} /// @name Mathematics