Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cleanup] Reduce build size + clean up loose ends #3655

Merged
merged 33 commits into from
May 28, 2021

Conversation

TD-er
Copy link
Member

@TD-er TD-er commented May 22, 2021

@giig1967g Should for sure also test the GPIO code as I tried to remove quite a bit of code duplication (quite a reduction of a few k in build size)
I really hope to not have changed the behavior.

TD-er added 20 commits May 21, 2021 16:26
By stripping of an extra parameter, the build size is reduced quite a lot.
Wrapping functions to convert `LabelType::Enum` into String into separate function to reduce build size
Remove some code duplication by streaming JSON from PROGMEM arrays of `LabelType::Enum`.
This makes the code more readable and also (slightly) reduces build size. (at least memory usage when serving JSON)
Each call to a function with a flash string, expecting `String` argument is adding a `String()` wrapper increasing build size.
Less memory allocations and streaming flash strings as such without converting to `String`
Macros including function calls with lots of variables tend to add quite a lot to the build size.
@TD-er
Copy link
Member Author

TD-er commented May 25, 2021

OK, I'll stop now as it gets larger and larger....

The "ESP8266 display" build is almost 37k smaller with this PR.
The biggest gain in size is with the ESP32 max ETH build, 146396 bytes smaller.

Still for the minimal OTA builds, roughly 5 - 8k must be stripped to make them fit again. (already made roughly 16k smaller for those builds)

The biggest change here is to have less conversions from flash string to String in the code.
When calling a function which expects a String, but calling it with a flash string, the compiler inserts a wrapper to convert it into a String object.

void foo(const String& bar) {...}

foo(F("bar")); // Compiler: foo(String(F("bar"))); 

This is also done when returning a function, or declaring a variable:

String options[2] = {F("1"), F("2") };
// Compiler:
// String options[2] = { String(F("1")), String(F("2")) };

String MyTypeToString(int value) {
switch (value) {
  case 1: return F("one"); // Compiler: return String(F("one"));
  case 2: return F("two"); // Compiler: return String(F("two"));
}
}

By returning a Flash String type for those functions, you only need a single conversion to a String when assigning it.
Similar, by writing to the Web Server TX buffer, there is no need to convert it to String first (needs memory allocation) but rather flush it directly to the TX buffer.
This appears to apply to almost all (!!) form selectors we use, thus saving a lot of String() wrappers in the code and also making execution faster as we now don't create String objects in an array, but rather an array of pointers to flash strings.

@TD-er
Copy link
Member Author

TD-er commented May 25, 2021

OK, could not leave it....
Now the FHEM/HA minimal OTA build is very very close to fit again (only 300 bytes when built on Windows....)

FHEM HTTP controller should be tested as the generated JSON now slightly differs from the old implementation.
The values used to be wrapped in quotes, but the JSON generated using the ESPEasy code (no longer using ArduinoJSON to serialize) is not wrapping the numerical values in quotes.
It is valid JSON and the FHEM command reference is not really clear on whether it MUST be sent as a string or as a numerical value.
JSON generated by FHEM does not wrap numerical values in quotes, but the examples used for input values do wrap it in quotes.

So if anyone using FHEM could test it, please do!
@TungstenE2 ???
@clumsy-stefan ???

This gets rid of another rather big chunk of (generated) code which does add up to the binary size.
@TD-er
Copy link
Member Author

TD-er commented May 26, 2021

@clumsy-stefan Added your suggestions and got rid of another rather big chunk of code by removing the addLog macro and adding separate functions with different log string type signatures.

@TD-er
Copy link
Member Author

TD-er commented May 26, 2021

Warning!
The error of not being able to click back to "home" has some serious other implications.

I made an error in the htmlEscapeChar function, so streaming rules to the browser may also strip out characters which will then be lost when saving the rules.

So wait a moment for a fix for this.!!!

@TD-er
Copy link
Member Author

TD-er commented May 26, 2021

And it should now be fixed.

@clumsy-stefan
Copy link
Contributor

Oh, ok... just updated all nodes ;) so I'll do a new version and update again...

Not sure about the AddLog (or at least I can't remember ;) but sounds good!!

One other code duplication was about the login, it seems it accepts basic-auth as well as the HTTP form-login..

@TD-er
Copy link
Member Author

TD-er commented May 26, 2021

Not sure about the AddLog (or at least I can't remember ;) but sounds good!!

I had created a macro, a long time ago, which does check for the loglevelactive before actually creating the log string.
That does save resources, but now that I filter on flash strings and check the log level active before casting it, it already reduces the amount of unneeded work, like the macro did.
And the check is now part of the cpp file, so it is only implemented once, instead of on every call of addLog.

The drawback of this approach is that constructed strings (or functions generating a String, which isn't used often in the call of addLog) will still be constructed. But that's already not so many anymore as most are wrapped in if (logLevelActiveFor(....)) checks anyway.
So this now saves quite a lot on builds with a lot of plugins. (roughly 5k in build size on 'max' build)

@clumsy-stefan
Copy link
Contributor

clumsy-stefan commented May 26, 2021

New sizes:

623248 May 18 15:31 ESPEasy.ino.1M_80Mhz_SMY_11.16.bin
604416 May 26 08:47 ESPEasy.ino.1M_80Mhz_SMY_11.17T2.bin
603456 May 26 13:17 ESPEasy.ino.1M_80Mhz_SMY_11.17T3.bin
935584 May 18 15:36 ESPEasy.ino.4M_80Mhz_SMY_11.16.bin
887920 May 26 08:31 ESPEasy.ino.4M_80Mhz_SMY_11.17T2.bin
885968 May 26 12:58 ESPEasy.ino.4M_80Mhz_SMY_11.17T3.bin

1-2k improvment again.

Main-Page link works again!

... And hopefully this may help in making Linux and Windows builds create more equal sized binaries.
@clumsy-stefan
Copy link
Contributor

603456 May 26 13:17 ESPEasy.ino.1M_80Mhz_SMY_11.17T3.bin
603376 May 26 17:31 ESPEasy.ino.1M_80Mhz_SMY_11.17T4.bin
885968 May 26 12:58 ESPEasy.ino.4M_80Mhz_SMY_11.17T3.bin
885872 May 26 17:11 ESPEasy.ino.4M_80Mhz_SMY_11.17T4.bin

Another few bytes saved with latest commit!

@clumsy-stefan
Copy link
Contributor

What I also found is, that the new commits in this PR increase memory usage. You can see in the graphs below, that since updating (around 12:00) Free memory is significantly lower (around 2k):

image

@TD-er
Copy link
Member Author

TD-er commented May 26, 2021

Some parts of the code check the max. free memory and adjust the logging/cache/web and controller queue behavior based on this.
Maybe the memory gets less fragmented by these changes, so maybe the behavior of those parts may have changed.

But on the other hand, scanning WiFi may still be active? Or perhaps the sending of data to a controller is not flushing?
Do you see changes here in behavior with the controller(s)?

@clumsy-stefan
Copy link
Contributor

I did not change anything in the config, so wifi scanning was active before and after the update. Also I did not notice any change in the controller, but I only use FHEM all others are not even compiled in.

Could it be, that things which are not anymore in the build (and reduce the size) need to be "calculated" at runtime and therefore use more memory?

@TD-er
Copy link
Member Author

TD-er commented May 26, 2021

Not that I can think of.
The only thing that may introduce slightly more 'load' is the change in logging.
But most of the logs are wrapped in if (logLevelActiveFor(....)) so there is only a gain in less memory allocations.

So all changes should mainly be beneficial as there is:

  • less code (in the binary) to convert flash string to String
  • less conversions to String as more code now also uses flash strings and keeps it as flash strings.

Since a conversion to String type needs to allocate memory, every conversion that is no longer needed is less load on the resources.

@clumsy-stefan
Copy link
Contributor

The latest build ran more or less stable for the last 12 hours. Only Problem I encountered was that the lower memory combined with bad WiFi receiption, a lot of interaction on the web pages or a lot of tasts/devices seems to generate exceptions from time to time.... But this seems not critical to me.

I'd vote in favor to include this PR as quickly as possible as the improvements are really significant especially for the small builds!

Thanks for the great work done here!!

@TD-er
Copy link
Member Author

TD-er commented May 27, 2021

Found some more possibilities, but those require changes in the Arduino String class as I mentioned here: esp8266/Arduino#7758 (comment)

I will later this evening merge it.

@@ -124,7 +124,7 @@ bool do_process_c004_delay_queue(int controller_number, const C004_queue_element
String postStr = do_create_http_request(
hostName, F("POST"),
F("/update"), // uri
F(""), // auth_header
EMPTY_STRING, // auth_header
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a generic improvement, simply replace all F("") by EMPTY_STRING ? Or does it require a __FlashStringHelper reference?

(If so then I'll start updating that in my work in progress 😉)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it is slightly more complex :)

For those functions that return a String object, this is an optimization as you don't need to create a String object, but simply reference a static const existing one.
But it is a bigger reduction in size if you can create functions that return a flash string instead of having code for every assignment of a flash string to a String object.

e.g. a typical toString function:

String toString(myEnumClassType enumValue) {
  switch (enumValue){
    case myEnumClassType::bla:
      return F("bla");
  }
}

This return will effectively generate return String(F("bla")); for every flash string return calls.
By returning a flash string type, you only cast it to String after the function is called. (or not at all if it can be kept as a flash string)
For example the webserver markup function calls that now also accept a flash string array, which only holds a pointer instead of allocating memory for the string only to dump it to the TXBuffer object, which also can handle flash strings without conversion.

That's the true change of this PR.

@TD-er TD-er merged commit 0d12e51 into letscontrolit:mega May 28, 2021
@TD-er TD-er deleted the bugfix/Uninitialized_variables branch May 28, 2021 20:12
@ghtester
Copy link

ghtester commented May 29, 2021

Only Problem I encountered was that the lower memory combined with bad WiFi receiption, a lot of interaction on the web pages or a lot of tasts/devices seems to generate exceptions from time to time....

Unfortunately the free memory was decreased significantly in my case. :-(
Today morning I have compiled the custom Vagrant build and tested on my primary ESP Easy node where 2 Controllers and 9 devices are configured. Before (earlier build from last Sunday with yet unmerged PR3655) there was usually about 8000 - 10000 bytes free, now it's only about 3000 - 5000 which does not allow a stable operation. :-(
Thanks a lot for your hard work, I appreciate the improvements, I'll keep testing but I am afraid I'll have to revert back to previous build.

Firmware .
Build:⋄ 20113 - Mega
System Libraries:⋄ ESP82xx Core 2843a5ac, NONOS SDK 2.2.2-dev(38a443e), LWIP: 2.1.2 PUYA support
Git Build:⋄ My Build: May 29 2021 07:52:24
Plugin Count:⋄ 35
Build Origin: Vagrant
Build Time:⋄ May 29 2021 07:51:25
Binary Filename:⋄ ESP_Easy_mega_20210529_custom_IR_ESP8266_4M1M
Build Platform:⋄ Linux-4.15.0-51-generic-x86_64-with-glibc2.27
Git HEAD:⋄ mega_0d12e51
Memory stats
Free RAM: 5472
Heap Max Free Block: 824
Heap Fragmentation: 74%
Free Stack: 3632
Boot: Exception (2)
Reset Reason: Exception
Last Action before Reboot: Background Task

@TD-er
Copy link
Member Author

TD-er commented May 29, 2021

Do you have "Periodical WiFi scan" enabled?
Lower memory is a serious concern and the more we can pinpoint what causes it the better

@ghtester
Copy link

No, it's disabled.
Regarding to Special and Experimental Settings, I have Force WiFi B/G: , Restart WiFi Lost Conn: and Periodical send Gratuitous ARP: checked.

@TD-er
Copy link
Member Author

TD-er commented May 29, 2021

Do you use mDNS in your builds? (to get a .local address)

@ghtester
Copy link

ghtester commented May 29, 2021

I have never needed it so unless it's a default option, the answer is NO. I did not enable it (and I can't find it anywhere).
I am just using Fixed IP Octet settings. Also the Email (SMTP) notification is configured (but activated only in very rare cases ). The node is placed in place where both Primary and Backup AP's RSSI is about -90 dBm...
BTW. after about 15 minutes after last reboot the Free RAM increased to about 5000 - 8000... So I'll keep testing this latest build if the node won't reboot often.

Edit - regarding to mDNS, I have found it should be enabled by #define FEATURE_MDNS. So I can confirm this definition does not exist in my Custom.h file.

@TD-er
Copy link
Member Author

TD-er commented May 29, 2021

OK, so we can eliminate that from the list of possible memory hogs.
Can you send me your list of used plugins and controllers? (can also via mail if you prefer)

@ghtester
Copy link

image
image
The plugin set from Custom.h file:
plugins.txt

@TD-er
Copy link
Member Author

TD-er commented May 29, 2021

And the queue depth of the controllers?
Do the controllers still work as expected, no noticable extra delays (may be indication of excessive use of the controller queues)

@ghtester
Copy link

Everything looks OK with controllers, intervals are 30s, 15s, 60s in plugins 6, 7, 8 and no visible delays encountered.
Well, perhaps it's time change the HTTP Controller's parameters as I did not modify it for years and in the meantime some useful queue options were added...

image
image

@ghtester
Copy link

ghtester commented May 29, 2021

I have temporarily disabled the Communication - TSOP4838 plugin, then the Free RAM increased to 10000 - 12000...
So it looks this plugin is now more RAM hungry although I don't use Add new received code to command lines: nor Execute commands: options.

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.

5 participants