Add a `force` proto field option that generates `_force` variants of
calc_ and encode methods, skipping the zero/empty check. Applied to
BluetoothLERawAdvertisement fields that are almost always non-default
(address, rssi, data) to eliminate dead branches on the BLE proxy
hot path.
On-device benchmarks show calculate_size improved from 12,649 ns to
10,982 ns per 12-advertisement batch (-13.2%) with only +8 bytes flash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The constexpr path and the noinline slow path shared the same
if/else cascade for values >= 128. Extract into a private
constexpr ESPHOME_ALWAYS_INLINE helper used by both.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Static methods use lower_snake_case without trailing underscore.
The trailing underscore convention is for member fields only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ESPHOME_ALWAYS_INLINE on these caused significant flash bloat in
cold calculate_size() callers (DeviceInfoResponse +117B, HelloResponse
+63B, ListEntitiesEventResponse +79B) while only benefiting the BLE
hot path marginally. Let the compiler decide when to inline these.
The varint() fast path remains force-inlined as that eliminates the
most expensive indirect calls on the hot path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split ProtoSize::varint() into an always-inlined fast path (value < 128)
and a noinline slow path, eliminating indirect function calls on the BLE
advertisement encode hot path. Also force-inline calc_uint32() and
calc_length() so the compiler fully inlines size calculations per
advertisement instead of emitting out-of-line calls.
Verified via Xtensa disassembly: eliminates ~80 indirect function calls
per 16-advertisement batch flush (3 calc + varint per advertisement).
Total hot path code reduced from 24 to 21 functions.
The capacity check in reserve()/resize() is the hot path and stays
inline via ESPHOME_ALWAYS_INLINE. The actual reallocation (make_buffer
+ memcpy) is a cold path outlined into grow_() to avoid bloating
every call site. resize() delegates to reserve() for the capacity
check since both inline to the same code.
The capacity check in reserve()/resize() is the hot path and stays
inline. The actual reallocation (make_buffer + memcpy) is a cold path
that should be outlined to avoid bloating every call site.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Avoid calling reserve() when capacity is already sufficient.
After warmup, resize() becomes just a compare + store with no
function call overhead on the hot path.
The API protobuf write path uses a shared buffer that gets resize()'d on
every message send. std::vector::resize() zero-fills new bytes, but every
byte is overwritten by the encoder before being read. For a 16-advertisement
BLE proxy batch, this wastes ~1300 bytes of memset per flush (~10x/second).
ProtoByteBuffer is a minimal replacement that skips zero-initialization on
resize(). On ESP32/RP2040/LibreTiny it also skips zero-fill on allocation
via make_unique_for_overwrite. On ESP8266 it falls back to make_unique
(zero-fills on alloc, but resize still doesn't zero-fill — the main win
is preserved since reserve is typically a no-op after warmup).
Remove value-initialization ({}) from StaticVector's underlying
std::array. Only elements [0, count_) are ever accessed, so
initializing the full array is wasted work.
This eliminates a memset on every construction. Most impactful for
stack-allocated StaticVectors in hot paths like
APIPlaintextFrameHelper::write_protobuf_messages, which was
memsetting 276 bytes of iovec storage on every BLE proxy flush.
Move ble_addr_to_uint64 from ble.cpp to ble.h as inline. This
eliminates the indirect call and Xtensa register window rotation
at all 3 call sites, most importantly in the BLE proxy hot path
(BluetoothProxy::parse_devices) which calls it per advertisement.
Saves 24 bytes of flash overall.