For non-first batch messages, fold the payload resize into the
reserve_and_resize call. This eliminates a redundant size() read,
a redundant capacity check, and an extra jump on the hot path.
Saves 17 bytes in encode_to_buffer (130 → 113) and ~5 instructions
on the second-message path.
schedule_message_front_ is only called from cold paths (on_shutdown,
check_keepalive_). Moving it out-of-line prevents add_item_front and
push_back from being inlined into those callers.
add_item_front is only called from on_shutdown and check_keepalive_
which are cold paths. Keeping it out-of-line prevents inlining
push_back into those callers.
Both are only called from one site each. Moving them inline lets the
compiler inline push_back directly into the caller without a function
call barrier. Removes the now-unnecessary push_item wrapper.
Add __attribute__((flatten)) to add_item and add_item_front so the
compiler inlines push_item → push_back and all their callees directly
into these functions, eliminating the function call barrier.
The out-of-line push_item with __attribute__((flatten)) inlined
push_back's callees into push_item, but push_item itself remained
a function call barrier. Moving push_item inline lets the compiler
inline push_back into the actual call sites (add_item, add_item_front).
Inlining push_item into add_item bloated the dedup loop, causing
the compiler to make worse inlining decisions. Total time went from
44µs to 64.5µs. Restore out-of-line with __attribute__((flatten))
which keeps add_item's dedup loop tight while still inlining
push_back's callees into push_item.
push_item was defined out-of-line in the .cpp to avoid duplicate
_M_realloc_insert instantiation, but the function call overhead
on every add_item was visible in benchmarks. Move it inline since
it's a one-liner wrapper around push_back.
should_send_immediately_ requires both should_try_send_immediately
flag AND batch_delay==0. Without setting batch_delay to 0, all
sends were falling back to the batch path.
Increase socket buffer to 16MB so benchmarks never hit WOULD_BLOCK
during an inner loop iteration. Remove per-iteration drain_socket
calls that were adding noise and causing the immediate path to
fall back to batching. Drain only between outer iterations.
Add benchmarks for the full send_sensor_state path covering both
immediate send and batch paths. This measures the end-to-end cost
of sending a sensor state update through an APIConnection including
entity field population, proto encode, framing, and TCP write.
bluetooth_proxy's loop() ran every main loop iteration (~7400 times/60s)
but only flushed advertisements every 100ms (~600 times/60s). The remaining
~6800 iterations just checked a timestamp and returned, wasting loop
framework overhead.
Replace with set_interval(100ms) so the component only runs when there's
work to do. Move flush_pending_advertisements to an inline method in the
header to avoid out-of-line call overhead in the hot parse_devices path.
Remove redundant is_connected/api_connection checks from flush since all
callers already guard.
The runtime_stats component used a std::map<Component*, ComponentRuntimeStats>
to record per-component timing data. Every loop iteration for every component
required a red-black tree lookup to record its time, adding ~2-3µs of
measurement overhead per component — often exceeding the actual work done
by lightweight components like OTA idle checks.
Move RuntimeStats struct directly onto Component (behind #ifdef USE_RUNTIME_STATS)
so recording is a direct member access with zero lookup cost. The
RuntimeStatsCollector now iterates App.components_ to collect stats instead of
maintaining its own map, eliminating ~2KB of red-black tree code.
The FreeRTOS timer command queue moves from heap to BSS on all
LibreTiny targets, not just BK72xx. RTL87xx and LN882x benefit
from the same heap savings.