Add APIBuffer::reserve_and_resize() to eliminate duplicate grow_()
capacity checks when reserve() is immediately followed by resize().
This saves one grow_() check per call site (~12 bytes each).
Simplify set_nodelay_for_message() Nagle batching state machine by
replacing the NODELAY_ON (-1) sentinel with a simple counter starting
at 0. Reduces branches from 5 to 3 with identical behavior verified
by exhaustive testing of all 2^N message sequences up to length 12.
Saves 32 bytes flash on ESP8266.
ESP-IDF places log_format_text.c in IRAM/DRAM via linker fragment
(noflash) when CONFIG_LOG_IN_IRAM=y. Our override is in a different
compilation unit so string literals would default to flash. In
constrained environments where flash cache is disabled, reading
flash-resident format strings would fault.
Move all format string constants to DRAM_ATTR to match ESP-IDF's
own behavior.
Switch from ESP-IDF Log V1 to V2, which centralizes log formatting
inside esp_log() instead of expanding esp_log_timestamp(), color codes,
and LOG_FORMAT() at every ESP_LOGx macro call site.
This saves ~9KB of flash by eliminating ~500 per-site macro expansions
in ESP-IDF library code (gpio, ethernet, mdns, uart, wifi, etc.).
Override esp_log_format() to skip ESP-IDF's own formatting after the
ESPHome logger hook is installed, since ESPHome does its own formatting.
For early boot and constrained environments (ISR, cache disabled),
format messages in ESPHome style with colors using a stack buffer.
LwIPLock was introduced for RP2040 WiFi in #14679 to prevent race
conditions between lwip callbacks and the main loop. However, on
platforms without lwIP core locking (ESP8266, LibreTiny, Zephyr,
RP2040 without WiFi), the constructor/destructor were empty stubs
in .cpp files that the compiler could not see through, generating
unnecessary function calls at every call site.
Move the no-op implementation inline into helpers.h so the compiler
can eliminate all LwIPLock overhead on these platforms. ESP32 and
RP2040+WiFi retain their out-of-line implementations with real
locking.
LwIPLock was introduced for RP2040 WiFi in #14679 to prevent race
conditions between lwip callbacks and the main loop. However, on
platforms without lwIP core locking (ESP8266, LibreTiny, Zephyr,
RP2040 without WiFi), the constructor/destructor were empty stubs
in .cpp files that the compiler could not see through, generating
unnecessary function calls at every call site.
Move the no-op implementation inline into helpers.h so the compiler
can eliminate all LwIPLock overhead on these platforms. ESP32 and
RP2040+WiFi retain their out-of-line implementations with real
locking.
Move get_name() and get_object_id_hash() definitions from
entity_base.cpp to entity_base.h so the compiler can inline
these trivial member accessors, eliminating call overhead at
every call site.
Force-inline calc_uint64_force, calc_sint32_force, calc_length_force,
and calc_message_force so the varint fast path (value < 128) is
expanded at each call site instead of going through a function call.
These are the size-calculation methods used by BLE proxy messages
(BluetoothLERawAdvertisement, BluetoothGATTService/Characteristic/
Descriptor) which are called 12x per advertisement batch in the
hot path.
Benchmarked on ESP32 with 12-advertisement batches (10k iterations):
- calculate_size: 10381 -> 9718 ns/op (6.4% faster)
- Full calc+encode: 49886 -> 47599 ns/op (4.6% faster)
Flash cost: +40 bytes (ESP32 IDF), +64 bytes (ESP8266).
Add a "Top Called Functions" section to the analyze-memory report
that shows the most frequently called functions by call site count.
This helps identify inlining candidates by showing which functions
are called most often alongside their code size.
The analysis parses objdump disassembly output to count direct and
indirect call instructions across architectures (Xtensa call0/callx0,
ARM bl/blx).
Also fixes _batch_demangle_symbols to merge into the existing cache
instead of replacing it.