[esp32_ble] Optimize BLE event hot path performance

- LockFreeQueue: Add fast-path check to get_and_reset_dropped_count().
  On Xtensa, uint16_t atomic exchange compiles to ~25 instructions with
  a CAS retry loop and memory barriers. A relaxed load (single instruction)
  short-circuits the common case where dropped_count is zero.

- BLEEvent: Remove redundant release() calls in load_*_event() methods.
  EventPool::release() already calls event->release() before returning
  events to the free list, so every event from allocate() is already clean.

- BLEEvent::release(): Only null heap_data on the delete path. Skip
  unconditional zeroing of is_inline and heap_data when data was inline
  (no heap allocation to clean up).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
J. Nick Koston
2026-03-08 09:24:10 -10:00
co-authored by Claude Opus 4.6
parent ea7cfffdda
commit 665b0143f9
2 changed files with 15 additions and 13 deletions
+6 -12
View File
@@ -155,44 +155,38 @@ class BLEEvent {
void release() {
switch (this->type_) {
case GAP:
// GAP events don't have heap allocations
// GAP events never have heap allocations
break;
case GATTC:
// Param is now stored inline, only delete heap data if it was heap-allocated
if (!this->event_.gattc.is_inline && this->event_.gattc.data.heap_data != nullptr) {
delete[] this->event_.gattc.data.heap_data;
this->event_.gattc.data.heap_data = nullptr;
}
// Clear critical fields to prevent issues if type changes
this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
break;
case GATTS:
// Param is now stored inline, only delete heap data if it was heap-allocated
if (!this->event_.gatts.is_inline && this->event_.gatts.data.heap_data != nullptr) {
delete[] this->event_.gatts.data.heap_data;
this->event_.gatts.data.heap_data = nullptr;
}
// Clear critical fields to prevent issues if type changes
this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
break;
}
}
// Load new event data for reuse (replaces previous event data)
// Note: release() is NOT called here because EventPool::release() already
// calls event->release() before returning to the free list. Every event
// from allocate() is already in a clean state.
void load_gap_event(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
this->release();
this->type_ = GAP;
this->init_gap_data_(e, p);
}
void load_gattc_event(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
this->release();
this->type_ = GATTC;
this->init_gattc_data_(e, i, p);
}
void load_gatts_event(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
this->release();
this->type_ = GATTS;
this->init_gatts_data_(e, i, p);
}
+9 -1
View File
@@ -104,7 +104,15 @@ template<class T, uint8_t SIZE> class LockFreeQueue {
}
}
uint16_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); }
uint16_t get_and_reset_dropped_count() {
// Fast path: relaxed load is a single instruction on all platforms.
// The atomic exchange (especially for uint16_t on Xtensa) compiles to
// an expensive sub-word CAS retry loop (~25 instructions + memory barriers).
// Since drops are rare, avoid the exchange in the common case.
if (dropped_count_.load(std::memory_order_relaxed) == 0)
return 0;
return dropped_count_.exchange(0, std::memory_order_relaxed);
}
void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); }