[api] Inline DeferredBatch::add_item to eliminate push_back call barrier (#15353)

This commit is contained in:
J. Nick Koston
2026-04-01 17:11:47 -10:00
committed by GitHub
parent 08c7b3afbd
commit 1436d034bf
2 changed files with 27 additions and 42 deletions
+3 -33
View File
@@ -132,8 +132,6 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
#endif #endif
} }
uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
void APIConnection::start() { void APIConnection::start() {
this->last_traffic_ = App.get_loop_component_start_time(); this->last_traffic_ = App.get_loop_component_start_time();
@@ -2072,37 +2070,9 @@ void APIConnection::on_fatal_error() {
this->flags_.remove = true; this->flags_.remove = true;
} }
void __attribute__((flatten)) APIConnection::DeferredBatch::push_item(const BatchItem &item) { items.push_back(item); } bool APIConnection::schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, return this->schedule_batch_();
uint8_t aux_data_index) {
// Check if we already have a message of this type for this entity
// This provides deduplication per entity/message_type combination
// O(n) but optimized for RAM and not performance.
// Skip deduplication for events - they are edge-triggered, every occurrence matters
#ifdef USE_EVENT
if (message_type != EventResponse::MESSAGE_TYPE)
#endif
{
for (const auto &item : items) {
if (item.entity == entity && item.message_type == message_type)
return; // Already queued
}
}
// No existing item found (or event), add new one
this->push_item({entity, message_type, estimated_size, aux_data_index});
}
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
// Add high priority message and swap to front
// This avoids expensive vector::insert which shifts all elements
// Note: We only ever have one high-priority message at a time (ping OR disconnect)
// If we're disconnecting, pings are blocked, so this simple swap is sufficient
this->push_item({entity, message_type, estimated_size, AUX_DATA_UNUSED});
if (items.size() > 1) {
// Swap the new high-priority item to the front
std::swap(items.front(), items.back());
}
} }
bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, bool APIConnection::send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
+24 -9
View File
@@ -644,11 +644,28 @@ class APIConnection final : public APIServerConnectionBase {
// Add item to the batch (with deduplication) // Add item to the batch (with deduplication)
void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size, void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
uint8_t aux_data_index = AUX_DATA_UNUSED); uint8_t aux_data_index = AUX_DATA_UNUSED) {
// Dedup: O(n) scan but optimized for RAM over performance
// Skip deduplication for events - they are edge-triggered, every occurrence matters
#ifdef USE_EVENT
if (message_type != EventResponse::MESSAGE_TYPE)
#endif
{
for (const auto &item : this->items) {
if (item.entity == entity && item.message_type == message_type)
return; // Already queued
}
}
this->items.push_back({entity, message_type, estimated_size, aux_data_index});
}
// Add item to the front of the batch (for high priority messages like ping) // Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size); void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
// Single push_back site to avoid duplicate _M_realloc_insert instantiation // Swap to front avoids expensive vector::insert which shifts all elements
void push_item(const BatchItem &item); this->items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED});
if (this->items.size() > 1) {
std::swap(this->items.front(), this->items.back());
}
}
// Clear all items // Clear all items
void clear() { void clear() {
@@ -713,7 +730,7 @@ class APIConnection final : public APIServerConnectionBase {
ActiveIterator active_iterator_{ActiveIterator::NONE}; ActiveIterator active_iterator_{ActiveIterator::NONE};
// Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary // Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary
uint32_t get_batch_delay_ms_() const; uint32_t get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
// Message will use 8 more bytes than the minimum size, and typical // Message will use 8 more bytes than the minimum size, and typical
// MTU is 1500. Sometimes users will see as low as 1460 MTU. // MTU is 1500. Sometimes users will see as low as 1460 MTU.
// If its IPv6 the header is 40 bytes, and if its IPv4 // If its IPv6 the header is 40 bytes, and if its IPv4
@@ -780,10 +797,8 @@ class APIConnection final : public APIServerConnectionBase {
} }
// Helper function to schedule a high priority message at the front of the batch // Helper function to schedule a high priority message at the front of the batch
bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) { // Out-of-line: callers (on_shutdown, check_keepalive_) are cold paths
this->deferred_batch_.add_item_front(entity, message_type, estimated_size); bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size);
return this->schedule_batch_();
}
// Helper function to log client messages with name and peername // Helper function to log client messages with name and peername
void log_client_(int level, const LogString *message); void log_client_(int level, const LogString *message);