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:
J. Nick Koston
2026-04-01 12:06:03 -10:00
parent 249a4bdcf0
commit 63a0581440
4 changed files with 21 additions and 1 deletions
@@ -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);
+4
View File
@@ -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_); }
+6
View File
@@ -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);