Closed khoih-prog closed 2 years ago
Hi @salasidis
I don't know if you're using something different, but as I test here, the HEAP is not x2
as you experienced
Arduino IDE v1.8.19
on Ubuntu 20.04 LTS
mbed_portenta v3.3.0
Start Async_AdvancedWebServer on PORTENTA_H7_M7 with Ethernet using Portenta_Ethernet Library
Portenta_H7_AsyncTCP v1.4.0
Portenta_H7_AsyncWebServer v1.3.0
Using mac index = 13
Connected! IP address: 192.168.2.123
HTTP EthernetWebServer is @ IP : 192.168.2.123
HEAP DATA - Pre Create Arduino String Cur heap: 7434 Res Size: 452048 Max heap: 7448
..HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 48611
Out String Length=31257
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82115
.HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82115
Out String Length=31277
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82135
HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82135
Out String Length=31229
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82135
.HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82135
Out String Length=31237
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82135
HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82135
Out String Length=31257
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82135
.HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82135
Out String Length=31247
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82135
HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82135
Out String Length=31198
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82135
.HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82135
Out String Length=31285
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82143
HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82143
Out String Length=31224
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 82143
.HEAP DATA - Pre Send Cur heap: 50426 Res Size: 452048 Max heap: 82143
Out String Length=31269
HEAP DATA - Post Send Cur heap: 50521 Res Size: 452048 Max heap: 83942
HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 83942
Out String Length=31261
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 83942
.HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 83942
Out String Length=31237
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: 83942
The current code is already using passing-by-reference
to avoid unnecessary copy. The copy must take place once, to finally store the String
to local var, to be sure the to-be-sent data
is still there after the original String
has been destroyed.
If you're interested, I'll post the full process
, so that you know the data flow (without unnecessary copy).
If you look at the top of your example
Start Async_AdvancedWebServer on PORTENTA_H7_M7 with Ethernet using Portenta_Ethernet Library
Portenta_H7_AsyncTCP v1.4.0
Portenta_H7_AsyncWebServer v1.3.0
Using mac index = 13
Connected! IP address: 192.168.2.123
HTTP EthernetWebServer is @ IP : 192.168.2.123
HEAP DATA - Pre Create Arduino String Cur heap: `7434` Res Size: 452048 Max heap: `7448`
..HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap `48611`
Out String Length=31257
HEAP DATA - Post Send Cur heap: 48706 Res Size: 452048 Max heap: `82115`
.HEAP DATA - Pre Send Cur heap: 48611 Res Size: 452048 Max heap: 82115
You can see that the max heap starts at 7.4k
Goes to 48k with the String reserve
Then goes to 82k when the send occurs.
So the max heap is increasing. I noticed sometimes it is only 2x and not 3x, but did not uncover when that is the case.
The heap is recovered after the code runs, but the problem is that the program will crash if a large enough string is passed that the max heap hits the 430-450k mark.
In my other example, If passing a c string, then the string has to first be created, so that uses Heap RAM (same as having created the string in the first place).
In my case, I am sending sensor data, and at 3 months worth, my heap grows to a max of 374k (basically another 60k, and I would expect crashes). However, the program is only using about 60-80k of total heap space when not sending data via the library.
My goal is to be able to send a 2Mb string (2 yrs of data) that is stored in SDRAM. In order to be able to do that, any string manipulations would have to avoid arduino strings, and do all the concatenating etc in place on the passed string (as mentioned though, the string buffer would have to be oversized enough to be able to take the added data)
Ah, I see your point now. The HEAP must expand because of the mandatory copy as mentioned here
The copy must take place once, to finally store the String to local var, to be sure the to-be-sent data is still there after the original String has been destroyed.
String
request->send(200, "image/svg+xml", out);
We could use other way to avoid copy (such as pointer, reference, etc), but we can not know if the original String out
is still there when needed !!! Therefore, copy to be sure.
If you can be sure the original String out
can be preserved, we can create another function to avoid the copy.
For example
request->sendConstantString(200, "image/svg+xml", `out`);
My goal is to be able to send a 2Mb string (2 yrs of data) that is stored in SDRAM. In order to be able to do that, any string manipulations would have to avoid arduino strings, and do all the concatenating etc in place on the passed string (as mentioned though, the string buffer would have to be oversized enough to be able to take the added data)
A little bit odd here, why you have to send 2MBytes (2 yrs of data) in one String ??? Why can't you divide it to many smaller pieces, or to send it more frequently ???
MPUs, with very limited resource, have to be used / treated very carefully and differently from normal PCs.
It would be best if there was a second function that could take a C string - that way the string can be sent straight from SDRAM. It would save a lot of memory, both for the sending of data, as well as the loading of the original web page (my web page is 70k long - after offloading a bunch of stuff to CDN).
I create the web page in an SDRAM string, and after my web page load, the heap goes to 200k in my case. I will unload some of the constant scripts to CDN as well - so this one is manageable, but for others where an even larger web page is created, or if CDN is not possible, then the proposed mod would help them as well.
In the data example, the sensor data is stored in the SD card. There are many sensors, and each one may have 2Mb of data. The web page received the data for making custom graphs. It is possible to break up the data into 8 chunks, but that would require 8 separate accesses to the SD card, with 8 file seeks to the correct positions ... Then the data has to be sent to the graphing widget piecemeal, which also takes time. It would all result in creating a graph that is likely 3-4x slower than doing it in one shot.
I tried to follow the sequence of the library from the send on out, and I think a c string would be possible, and the only manipulation that was required was adding I believe a header? and a footer (? terminology). Both of those would be done with an in place shift copy of the original string, then copy the header (without the trailing '\0', followed by a strcat of the footer. I think it would be reasonably fast, and would definitely not take any extra memory, except for the small header and footer strings.
What do you think?
Robert
I tried to follow the sequence of the library from the send on out, and I think a c string would be possible, and the only manipulation that was required was adding I believe a header? and a footer (? terminology).
This is where the real problem is. The response must have, besides String out
, all the baggage such as header
, footer
, etc.
So it's complex, but not impossible, to use original String out
or cString
as you have to concatenate the baggage + String/cString into one final String.
You also have to know ahead the header
, footer
, etc. lengths and reserve the continuous space (right before / after) the original String/cString
.
I don't know if it's worth the time to do this, while the simpler solution is to shorten the original String
by well planning.
Another possible way to do is to create a function sendRawString(), where you can
header
, footer
, etc. by calling newly created functions getHeader(), getFooter(), etc.out
);The function just keep the original String/cString
and keep it to be sent unconditionally, no question asked.
Hi @salasidis
After some more serious thinking, I'm be sure this can be done, but I'm sorry I don't have the time and resource to do this for a special use-case.
I'd appreciate if you can modify the library to adapt it to your use-case, then make a PR.
I'm closing the issue now as won't fix
, and waiting for your PR.
Good Luck,
Thanks for looking into it. (not sure if this is viewed after the issue has been closed - but here goes)
I tried single stepping though the library using a j-Link debugger, but ? because of compiler code optimizations ? it does not seem to follow a pattern that I would have expected.
As I have no experience writing, or modifying libraries, would it be possible to provide some pointers as to what parts of the library would need to be modified.
My best guess as to how to approach this would be to overload the send (and the functions that are called by it) to accept a c string.
In AsyncBasicResponse::_respond and AsyncBasicResponse::AsyncBasicResponse a new copy of the string content is created as _content (this is the main source of the problem I think). I would simply change content to a pointer to the c string. I would leave all the the header etc info as is, as the sizes of those strings are minimal. Then everywhere where the content is concatenated or manipulated, I would simply change it to C string manipulations. All the writes etc are done as a c_str() in any case, so those would be left as is.
Am I way off base, that the only changes required would be in the 2 functions above?
Any help would be appreciated. Thanks again.
In AsyncBasicResponse::_respond and AsyncBasicResponse::AsyncBasicResponse a new copy of the string content is created as _content (this is the main source of the problem I think). I would simply change content to a pointer to the c string. I would leave all the the header etc info as is, as the sizes of those strings are minimal. Then everywhere where the content is concatenated or manipulated, I would simply change it to C string manipulations. All the writes etc are done as a c_str() in any case, so those would be left as is.
Am I way off base, that the only changes required would be in the 2 functions above?
Basically correct. You have to try and modify step-by-step, then you'll be OK.
You can create a new class, similar to AsyncBasicResponse
, to deal only with the cString, certainly with all the functions / vars to support.
I certainly can help on the way, if necessary.
I'll be happy to have a new co-author
of this library, as well as many other AsyncWebServer libraries which can benefit by the similar enhancement. Looking forward to receiving your PRs.
Thanks for the support.
I will go the easiest route I know, which is to add a variable in the AsyncBasicResponse class called char * _clientCstr. When a send is called, that has a String as a argument, it will set this to NULL, and if the send that accepts a C string is called, it will set it to the string address.
In the routines mentioned above, I will simply check if _clientCstr is null or not, and use it, or the regular _client arduino String. Should require the most minimum of extra code hopefully.
I will keep you posted.
Robert
Tried to create a pull request (first time), but it created a fork - not sure if that is what I was supposed to do.
In any case, the finished code is here
https://github.com/salasidis/Portenta_H7_AsyncWebServer/blob/main/README.md https://github.com/salasidis/Portenta_H7_AsyncWebServer
As per the Readme file - I am getting 66k instead of 374k max heap space in my application. I should havbe no problem sending 2M files, or web pages larger than the main RAM
Since I create the web page once, and reuse, I make a second copy of the web page, so that when the send happens (which messes wit the actual string), I simply recopy C string C string for the next send - fast, easy, and almost no code changes required (none if you don't wish to use the features 0 fully backwards compatible I think)
request->send(200, "text/html", html_out, false);
strcpy(html_out, html_out_bak);
I temporarily created a PR to have an easier look of the files.
LGTM. You've written very good code.
Can you make some minor changes to match with the current styles
AWS_LOGDEBUGx
instead of Serial.printxx
README.md
correctly by adding a note how to use the new feature / functionBTW, to create a PR, after you've forked the library and have done all the mods, just press Pull Requests
(upper left corner, next to Code
, and proceed. Just so simple for you now.
I'm deleting the PR and let you do it.
Check the RSMOD branch for the modified code. Test / modify as necessary. Then create a new PR
I see you made all the changes already, thanks.
I had a question about the original software.
If you look at line 447 and 450 in the AsyncWebResponses.cpp file. The original program sets the _content String to String().
I had decided to do the same with the Cstring, by doing a _contentCstr = '\0';
What that did was that on the last sent packet, there was a '\0' on the string being sent embedded into the file. When I commented it out, the code worked OK (that is why I added all the debug serial prints - to try to see if that was the source of the error.
The question is, is the same thing happening in the original code, except the string has not been reclaimed by the heap yet, so the error does not show up???
Does it need to be there ??
If you look at line 447 and 450 in the AsyncWebResponses.cpp file. The original program sets the _content String to String().
Don't see that line.
Do you mean Portenta_H7_AsyncWebRequest.cpp
?
Could you also add examples to demo the new functions and better HEAP
benefits ?
Can be based on those 2 original examples you posted, Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino
and Async_AdvancedWebServer_MemoryIssues_Send_CString.ino
No should be in Portenta_H7_AsyncWebResponses.cpp
In the mod file it is the following
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time)
{
...
...
...
if (_contentCstr)
{
_writtenLength += request->client()->write(_contentCstr, available);
//_contentCstr[0] = '\0'; // <----- this gave error described until commented out
}
else
{
_writtenLength += request->client()->write(_content.c_str(), available);
_content = String(); // I<---- had placed it because of this - should this be there ?
}
_state = RESPONSE_WAIT_ACK;
return available;
}
`
Will try to add the files to the RSMOD examples - will run them first to make sure
Worked nicely -= went from 111k to 12.7k max heap
I uploaded them to my fork, as it sad I had no push access to add them to RSMOD
Can you take them from there?
https://github.com/salasidis/Portenta_H7_AsyncWebServer/tree/main/examples/Ethernet
I uploaded them to my fork, as it said I had no push access to add them to RSMOD
That's the way it must be. Only author / collaborator can modify any branch. You can only make PR.
Can you take them from there? https://github.com/salasidis/Portenta_H7_AsyncWebServer/tree/main/examples/Ethernet
I can. But it's better you make a PR for everything as you'll use the PR many more times.
I did a pull request, but I don't know how to merge your changes on my end (it says conflicts - and no idea how to resolve them). So, it did a pull request for the files you already fixed up as well.
You just either
repo
dirrectory. Then upload to your github. OrResolve conflicts
at the end of PR then go thru every conflict, delete all the markers and to-be-deleted code, then pressMarked as solved
for every conflicting file.For example, src/Portenta_H7_AsyncWebRequest.cpp
, it'll show
<<<<<<< RSMOD <============= to be deleted
if (nonDetructiveSend)
{
send(beginResponse(code, contentType, String(content))); // for backwards compatibility
}
else
{
send(beginResponse(code, contentType, content));
}
}
======= <============= to be deleted until >>>>>>> main
if (nonDetructiveSend == true) {
send(beginResponse(code, contentType, String(content))); // for backwards compatibility
} else {
send(beginResponse(code, contentType, content));
}
}
>>>>>>> main
then you delete all the markers (<<<<<<< RSMOD
, =======
, >>>>>>> main
) and the code between =======
and >>>>>>> main
within to make it as follows
if (nonDetructiveSend)
{
send(beginResponse(code, contentType, String(content))); // for backwards compatibility
}
else
{
send(beginResponse(code, contentType, content));
}
}
until all markers are deleted, then press Marked as resolved
Repeat for all 4 files. Then you'll be ready to merge to your GitHub
Pull Requested the NO SDRAM file.
Also, have you had a chance to look at the code below - should I try to see how it works eliminating the line ****
_writtenLength += request->client()->write(_contentCstr, available);
//_contentCstr[0] = '\0'; // <----- this gave error described until commented out
}
else
{
_writtenLength += request->client()->write(_content.c_str(), available);
_content = String(); // I<---- had placed it because of this - should this be there ? ****
}
`
Closed as completed via #8
As you mentioned in Arduino Forum AsyncWebServer Library - heap memory use
If the string is to be used again, rather than recreating it, I check if the first 10-20 characters have changed, if not, the string is still good, otherwise - you have to recreate it.
Do you think we can reuse the cString by just changing the address, such as creating a new private pointer and update it according to the change ?
For example, change
to
char *_contentCstr; // RSMOD
char *new_contentCstr;
and in AsyncBasicResponse::_respond(AsyncWebServerRequest *request)
, update the pointer by, e.g., outLen
is the final shift, then update the pointer at the end of the function
new_contentCstr = cStr + outLen;
Just an idea, I haven't tested and verified.
BTW, I just ported your PR into AsyncWebServer_RP2040W library for RP2040W (WiFi) and OK now with the heap
The preliminary results are
Start Async_AdvancedWebServer_MemoryIssues_Send_CString on RASPBERRY_PI_PICO_W with RP2040W CYW43439 WiFi
AsyncTCP_RP2040W v1.1.0
AsyncWebServer_RP2040W v1.1.2
Connecting to SSID: HueNet1
SSID: HueNet1
Local IP Address: 192.168.2.74
Country code: XX
HTTP EthernetWebServer is @ IP : 192.168.2.74
HEAP DATA - Pre Create Arduino String Cur heap: 193000 Res Size: 150928 Max heap: 42072
..
HEAP DATA - Pre Send Cur heap: 193000 Res Size: 149176 Max heap: 43824
HEAP DATA - Post Send Cur heap: 193000 Res Size: 149048 Max heap: 43952
HEAP DATA - Post Send Cur heap: 193000 Res Size: 149032 Max heap: 43968
........ .
HEAP DATA - Post Send Cur heap: 193000 Res Size: 149024 Max heap: 43976
......... .......... ..........
Start Async_AdvancedWebServer_MemoryIssues_SendArduinoString on RASPBERRY_PI_PICO_W with RP2040W CYW43439 WiFi
AsyncTCP_RP2040W v1.1.0
AsyncWebServer_RP2040W v1.1.2
Connecting to SSID: HueNet1
SSID: HueNet1
Local IP Address: 192.168.2.74
Country code: XX
HTTP EthernetWebServer is @ IP : 192.168.2.74
HEAP DATA - Pre Create Arduino String Cur heap: 193256 Res Size: 191192 Max heap: 2064
.
HEAP DATA - Pre Send Cur heap: 193256 Res Size: 149432 Max heap: 43824
HEAP DATA - Post Send Cur heap: 193256 Res Size: 118056 Max heap: 75200
HEAP DATA - Post Send Cur heap: 193256 Res Size: 118024 Max heap: 75232
...
HEAP DATA - Post Send Cur heap: 193256 Res Size: 118016 Max heap: 75240
..
I thought about it on my end as well.
I am not sure how often the header is larger than the first packet, but, since that is the only time.
Can create a second string that has the remnant header (could be an arduinovstring, as likelv<1k.
Then when relooping into code, check if the remnant header is empty, and if not, make it the new header, and zero length the remnant string.
Then can continue running the code as before.
Would take only maybe 3-4 lines new code, and no rewrites in the main body.
We would then never need to care about having space in the larger string, and would allow the elimination of all the memmove calls.
Looking at the code, I think we need a combination.
Going in order,
Section 1 - no memmoves - content is 0 length - no change
Section 2 - the content and header all fit in 1 packet- we can just create an Arduino string and send it (the old way) - we lose a packet worth of heap, so not much
In Section 3 - the content length is unknown, but the the header is long, so we can do what I mentioned, send the partial header, and create a remnant header string to send in the RESPONSE_CONTENT loop
In Section 4 - Header is small, but content could be large - Leave as is - as the header is sent with a little added buffer
Section 5 - default - not sure why it would ever get here - to me the other instances seem to cover all the bases. But we would have to create a new header from the old, and proceed as in 3
In RESPONSE_CONTENT
before entering if/else statements
if there is no remantHeader use the loop as is
if there is a remnant header - it is easiest to just send the remnant header as a short packet, and leave remaining space as the _contentStr length - then continue with no mods
I can try all this later this week and let you know
Sounds good. It's worth a try. Waiting for results form your mods and tests.
Send a pull request with the mods - not fully tested yet, but works on my main application
Non destructive, no extra space needed
I ran the Cstr example, and it works.
Max Heap 12795 - and seems to stay stable
Your PR is very contagious ;=)), has been spreading to AsyncWebServer_WT32_ETH01 library for WT32_ETH01 (ESP32 + LAN8720A) and helps a big deal to save the heap.
The current results are
Start Async_AdvancedWebServer_MemoryIssues_Send_CString on WT32-ETH01 with ETH_PHY_LAN8720
AsyncWebServer_WT32_ETH01 v1.6.0 for core v2.0.0+
ETH Started
ETH Connected
ETH MAC: A8:48:FA:08:4B:FF, IPv4: 192.168.2.76
FULL_DUPLEX, 100Mbps
HTTP EthernetWebServer is @ IP : 192.168.2.232
HEAP DATA - Pre Create Arduino String Max heap: 326680 Free heap: 216212 Used heap: 110468
.
HEAP DATA - Pre Send Max heap: 326680 Free heap: 212292 Used heap: 114388
HEAP DATA - Post Send Max heap: 326680 Free heap: 205848 Used heap: 120832
.
HEAP DATA - Post Send Max heap: 326680 Free heap: 205816 Used heap: 120864
..
HEAP DATA - Post Send Max heap: 326680 Free heap: 205812 Used heap: 120868
...... ..
HEAP DATA - Post Send Max heap: 326680 Free heap: 205804 Used heap: 120876
........ .......... .......... .......... .......... .......... ..........
Out String Length=31282
.......... .
Start Async_AdvancedWebServer_MemoryIssues_SendArduinoString on WT32-ETH01 with ETH_PHY_LAN8720
AsyncWebServer_WT32_ETH01 v1.6.0 for core v2.0.0+
ETH Started
ETH Connected
ETH MAC: A8:48:FA:08:4B:FF, IPv4: 192.168.2.76
FULL_DUPLEX, 100Mbps
HTTP EthernetWebServer is @ IP : 192.168.2.232
HEAP DATA - Pre Create Arduino String Max heap: 326952 Free heap: 256484 Used heap: 70468
.
HEAP DATA - Pre Send Max heap: 326952 Free heap: 212600 Used heap: 114352
HEAP DATA - Post Send Max heap: 326952 Free heap: 174864 Used heap: 152088
..
Send a pull request with the mods - not fully tested yet, but works on my main application
Haven't seen anything yet. PR forgotten ???
Sent it again, if you did not get it, I am doing it wrong :-(
Only 2 files changed
https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/11
Great. Got it now. Will have a look later today.
Closed as done in Portenta_H7_AsyncWebServer v1.4.1
Looking forward to receiving many more of your PRs, issue reports, enhancement requests, etc.
Best Regards,
Hi @salasidis
Your PRs has just been spreading to AsyncWebServer_Ethernet library for ESP8266 and W5x00 / EMC28J60 Ethernet and helps a big deal to sned bigger data because of heap saving. ESP8266's heap space is much smaller than ESP32's
Posted by @salasidis as request->send(200, textPlainStr, jsonChartDataCharStr); - Without using String Class - to save heap #2 in Portenta_H7_AsyncTCP library
Is your feature request related to a problem? Please describe.
I have a relatively web page - about 100kBytes. In addition, I need to send large amounts of Json data - about 1Mbyte sensor data via the async web interface.
I save all the data in the SDRAM (the web page as well as the Json data string (created from SD Card logged data).
When responding to the request via request->send(200, textPlainStr, jsonChartDataCharStr); // jsonChartDataCharStr is a C type char * string
The library converts the (char *) array (JSon or the web page) into a String class variable before sending. This results in large amounts of heap use, and basically I cannot send more than 4 months of telemetry data (whereas the design calls for at least 2 years).
Describe the solution you'd like
I would like to have a request->send call that looks at the variable passed, and if it is a char * leaves it as such, without changing it to a String. Not only will this be faster, but it will also use a LOT less memory
Describe alternatives you've considered
My alternatives of making many calls to retrieve the data in small batches would add needless complexity, and also require multiple accesses to the SD card, which I want to avoid
Additional context
This would not be an issue if heap space was not so critical, but the portenta only has a small finite amount, and this library consumes about 80+% of it in my application in a single function call.
It would be nice if any mallocs could be limited, or at least made to optionally use the SDRAM, and not the main RAM.
Also any string manipulations (adding a header etc) could be done in place - just having to ensure that the passed string is at least a minimum size larger than the contents it holds.
On the default program, I added some code to print the heap size, and I increased the graph array from 50 to 500 points.
On the modified program, I created a C string instead of an Arduino String, and sent the same 500 line graph svg file
There is a word file that shows the outputs, and the outputs demonstrate that if sending an Arduino String, the software requires 2x extra heap space on top of the the actual string size.
If sending a C string, it takes 3x.
My proposal is to have an option (separate call, or modify the current ones), that can use the c string as is, and simply pre and post append to it any headers and footers required, without creating new Arduino Strings in the library
File Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino
File Async_AdvancedWebServer_MemoryIssues_Send_CString.ino
Heap Data when Sending a 500 line string
Heap Data when Sending a 500 line string (string is 31.2k long). Max Heap = 7.4k – before executing graph routine Max Heap = 48.5k – after reserving 40k for out string Max Heap = 111.2k – after send When sending am Arduino String, the library requires 2x the String length in extra heap.
The second program modifies the source code, so that instead of creating the out String, I create a C string called cStr. This has a buffer allocated in SDRAM. I then create the cStr in a similar fashion as before, except using strcat etc. (I remove the meta .. refresh – just to make the output cleaner)
When running the code – a 500 line graph string is sent here as well – the string is again 31.2k long Max Heap = 8.4k – before executing graph routine Max Heap = 13.5k after creating the string, but before send *** note that there is no heap increase as before Max Heap = 107.5k – after send
When sending a c string, the web server uses 3x the c string size iin heap space (1x more than before, as it makes a String out of the c string before proceeding