Reduce RAM use of Options/Hardware loading #2080
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Replaces the 8K static RAM allocation used for loading the Options/Hardware from flash, and replaces it with a buffered flash reader Stream that ArduinoJson can use to load the documents instead. The purpose of this optimization is to free up memory on ESP hardware, which has problems loading the webui on 3.2.0 in AP mode.
First of three PRs modifying the OPTIONS / devWifi libraries to reduce memory use.
Details
On a HappyModel EP2 with no home network, the webui fails to fully load. The page loads, the CSS loads, the javascript loads, but generating
/config
just hangs. If you wait long enough (many minutes) and hit the page again it seems to be OK (fewer simultaneous requests?) but who will wait that long?Checking the RAM usage, it isn't even getting to our handler, and when it does, there's about 8.3KB of RAM available, dropping to 3.7KB by the end of the function. We just need more available RAM. The lowest hanging fruit is this giant 8KB buffer that's only used once at boot. I changed this to simply do a
malloc()/free()
and it worked, but allocating this big chunk off the heap and freeing it created an 8KB hole, followed by a few small allocations from the loader, then the rest of the free RAM. The fragmentation is an issue because it essentially bifurcates the RAM into two memory pools since it is no longer contiguous.There were some other issues as well with the existing code. It allocates 8KB, only reads 2KB from flash, and the code expects that it has 16+64+512+2048 bytes. Casting the buffer to
const char *
also made ArduinoJson regard the buffer as read-only, so it would allocate its own RAM and copied the buffer into it, doubling up on allocations.There's no reason to read it all at once though, since ArduinoJson can load from Stream classes. I tried but could not get a
const __FlashStringHelper *
to bind to the correct deserializer, because I am pretty sure that would work without any new classes from us. Instead I created a EspFlashStream class that buffers 4 bytes as it reads them from flash. All of this memory allocation is strictly off the stack too, so no heap fragmentation.Now when the
/config
page loads, there's over 16KB or RAM available vs 8.3KB before. The AP mode webui loads for me every time now.Faster than the original too?!
options_init()
in 9901us +/- a couple usI stuck with a 4 byte buffer since it was already faster and 0.5ms isn't a huge deal.
Code Reorg
product_name
on the sketch (it tested the[0]
offset of the passed buffer, not the 4 bytes at its starting location). It now will try to load hardware JSON if there looks to be a string there, even if there was no product name flashed in.For future work I'd like to remove the String copies of
getOptions()
andgetHardware()
from RAM. Doing that made this hard to read patch even harder to follow. That will move this code so getOptions and getHardware return a Stream which may represent a SPIFFS file or a EspFlashStream, both of which can be used by the webserver and ArduinoJson without having their RAM hanging around all the time.Things that might look wrong
file.close()
calls have been removed. SPIFFS file implementation classes automatically close the file in their destructor when they go out of scope. I tested this to be sure it is working so there's no need for us to micromanage the file handle in our code.strlcpy()
. If they weren't null terminated, they will still not overrun the buffer. If they were null terminated then we'll have some undefined bytes in RAM after the string, but it should not be an issue since everything else handles them as null-terminated. Huge security flaw! An attacker could see what was in the firmware binary after the string, if they can somehow trick us into spitting it out. π€·ββοΈ