Skip to content

perf: drop <sstream> from JsonSettings, freeing 256KB of flash - #47

Open
socquique wants to merge 1 commit into
jhoff:mainfrom
socquique:drop-sstream
Open

perf: drop <sstream> from JsonSettings, freeing 256KB of flash#47
socquique wants to merge 1 commit into
jhoff:mainfrom
socquique:drop-sstream

Conversation

@socquique

Copy link
Copy Markdown

Problem

JsonSettings::getIntVector() / putIntVector() used std::istringstream and
std::ostringstream to handle a comma separated list of small integers.

Including <sstream> pulls the C++ iostreams and std::locale machinery into the
image, and with it newlib's wide-character and floating point printf/scanf
families. The largest symbols actually linked into the esp32_c3 build:

8.9 KB  _vfprintf_r        8.7 KB  _svfprintf_r      8.6 KB  _svfwprintf_r
8.0 KB  __ssvfscanf_r      6.7 KB  __ssvfiscanf_r    5.3 KB  _vfiprintf_r
5.1 KB  _svfiprintf_r      3.3 KB  _dtoa_r           3.3 KB  _strtod_l
2.4 KB  std::locale::_Impl::_Impl (x2)

None of that is used by this project. _svfwprintf_r is wide-character printf.

Change

Hand-rolled strtol parsing and String concatenation instead.

esp32_c3 firmware.bin   1,299,024 -> 1,042,608 bytes
                            99.1% ->     79.5% of the partition
                                        -256,416 bytes

That is the difference between an image with 11 KB of headroom and one with 268 KB.
The esp32_c3 build was close enough to its partition that adding almost anything
would have overflowed it.

Parsing is equivalent for well formed input; it stops at the first non-numeric
token instead of throwing, so it no longer needs std::stoi's exceptions.

Bounds check

That leniency exposes an existing hole, fixed in the same commit:
SplitFlapDisplay::init() sizes its loops from moduleCount but indexed the
address and offset vectors without checking their length, reading past the end of
the vector for a short or malformed list. Both now fall back to per-module
defaults, and numModules is clamped to MAX_MODULES so it cannot overrun
modules[].

Testing

Parser checked against the real setting values and edge cases — well formed lists,
embedded spaces, trailing comma, empty string, single value. Built and run on an
8 module ESP32-C3 display; addresses and offsets load identically.

Touches JsonSettings.cpp alongside the settings PR; trivial to rebase whichever
lands second.

getIntVector()/putIntVector() used std::istringstream/ostringstream to
handle a comma separated list of small integers. Including <sstream>
drags in the C++ iostreams and locale machinery, and with it newlib's
wide-character and floating point printf/scanf families - _svfwprintf_r,
__ssvfscanf_r, _vfprintf_r, _dtoa_r and friends. Hand-rolled strtol
parsing removes all of it: 254,816 bytes of flash, taking the esp32_c3
image from 99.2% to 79.7% of its partition.

Parsing is equivalent for well formed input and stops at the first
non-numeric token instead of throwing, so it no longer needs std::stoi's
exceptions either.

That leniency exposed an existing hole: SplitFlapDisplay::init() sized
its loops from moduleCount but indexed the address and offset vectors
without checking their length, reading past the end for a short or
malformed list. Both now fall back to per-module defaults, and
numModules is clamped to MAX_MODULES so it cannot overrun modules[].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant