mirror of
https://github.com/esphome/esphome.git
synced 2026-09-26 14:30:23 +00:00
Add empty_relaxed() fast path to wifi_loop_ queue drain
On Xtensa (ESP32), even relaxed atomic loads emit memw instructions, but they avoid the more expensive acquire fences. The pop() path required acquire loads + release stores just to discover the queue was empty. By checking empty_relaxed() first, the steady-state connected path (queue always empty) returns in ~7 instructions instead of falling through to get_and_reset_dropped_count() and pop().
This commit is contained in:
@@ -716,6 +716,11 @@ const char *get_disconnect_reason_str(uint8_t reason) {
|
||||
}
|
||||
|
||||
void WiFiComponent::wifi_loop_() {
|
||||
// Fast path: relaxed check avoids acquire fences (4x memw on Xtensa) when queue is empty.
|
||||
// Safe from consumer side — worst case we process events one loop iteration late.
|
||||
if (this->event_queue_.empty_relaxed())
|
||||
return;
|
||||
|
||||
uint16_t dropped = this->event_queue_.get_and_reset_dropped_count();
|
||||
if (dropped > 0) {
|
||||
ESP_LOGW(TAG, "Dropped %u WiFi events due to buffer overflow", dropped);
|
||||
|
||||
@@ -778,7 +778,12 @@ network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask(
|
||||
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; }
|
||||
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; }
|
||||
void WiFiComponent::wifi_loop_() {
|
||||
// Check for dropped events due to queue overflow
|
||||
// Fast path: skip queue drain when empty.
|
||||
// On LockFreeQueue platforms, relaxed loads avoid memory fences.
|
||||
// On FreeRTOSQueue platforms, this is a lightweight uxQueueMessagesWaiting check.
|
||||
if (this->event_queue_.empty_relaxed())
|
||||
return;
|
||||
|
||||
uint16_t dropped = this->event_queue_.get_and_reset_dropped_count();
|
||||
if (dropped > 0) {
|
||||
ESP_LOGW(TAG, "Dropped %" PRIu16 " WiFi events due to buffer overflow", dropped);
|
||||
|
||||
@@ -82,6 +82,10 @@ template<class T, uint8_t SIZE> class FreeRTOSQueue {
|
||||
|
||||
bool empty() const { return uxQueueMessagesWaiting(this->handle_) == 0; }
|
||||
|
||||
/// Fast empty check — same as empty() for FreeRTOS queues since
|
||||
/// uxQueueMessagesWaiting is already a lightweight read.
|
||||
bool empty_relaxed() const { return this->empty(); }
|
||||
|
||||
bool full() const { return uxQueueSpacesAvailable(this->handle_) == 0; }
|
||||
|
||||
size_t size() const { return uxQueueMessagesWaiting(this->handle_); }
|
||||
|
||||
@@ -118,6 +118,12 @@ template<class T, uint8_t SIZE> class LockFreeQueue {
|
||||
|
||||
bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); }
|
||||
|
||||
/// Fast empty check using relaxed loads — no memory fences on Xtensa.
|
||||
/// Safe when called only from the consumer side: head_ is only written by the
|
||||
/// consumer, and tail_ can only advance. Worst case we miss a just-pushed item
|
||||
/// and catch it next loop iteration.
|
||||
bool empty_relaxed() const { return head_.load(std::memory_order_relaxed) == tail_.load(std::memory_order_relaxed); }
|
||||
|
||||
bool full() const {
|
||||
uint8_t next_tail = next_index(tail_.load(std::memory_order_relaxed));
|
||||
return next_tail == head_.load(std::memory_order_acquire);
|
||||
|
||||
Reference in New Issue
Block a user