I'm seeing strange behaviour on my esp8266 over time. Multiple JSON buffer being mangled etc. What is the recommended way to manage memory on the esp8266 with ArduinoJson?
My recommendation would be to use a DynamicJsonBuffer with an initial size that correspond to your needs:
DynamicJsonBuffer jsonBuffer(4000);
However, I don't think the issue you're having will be fixed like this; it looks more complicated.
I'm also wondering this. I'm seeing some fairly heavy heap usage adding doubles to an array. For example adding 300 doubles to an array (which has a memory footprint of 1.5k) will eat up nearly 20k of heap! Which seams like a lot. Does giving a size to the dynamic buffer help that?
Could u elaborate a bit more so I understand what's going on?
First of all, in version 5.0.5, I changed the storage type from double to float because the overhead wasn't worth it. In older versions, the memory consumption was nearly twice bigger on ESP8266.
You can switch back to the double storage by editing ArduinoJson/Internals/JsonFloat.hpp.
Second remark, in version 5.0.6, I added the ability to set the initial size of DynamicJsonBuffer and changed the default from 32 to 256.
From there, I'm going to assume that you're using Arduino 5.0.6+ and kept the default float storage.
A JsonArray is just a linked list of JsonVariant.
So on an ESP8266:
float is 4 bytes, so 300 of them is 1.2kJsonVariant is 8 bytes, so 300 of them is 2.4kJsonArray has an initial size of 8 bytes.So the total is 3608 bytes, which can be confirmed by:
static_assert(JSON_ARRAY_SIZE(300) == 3608, "@bblanchon is full of sh*t");
Now, this is too big for the stack of the ESP8266, so using a DynamicJsonBuffer is appropriate.
DynamicJsonBuffer allocates blocks of memory using malloc(); each block is like a StaticJsonBuffer.
As soon as it doesn't find enough memory in a block, it allocates a new one twice as big (like std::vector).
In our case, it will allocate four blocks:
Total: 3888 bytes
But the actual memory usage will be slightly higher due to the plumbing of malloc() and free().
Also it introduces memory fragmentation that reduces the memory you can actually use.
Specifying the right initial size will allow DynamicJsonBuffer to make only one malloc():
The benefits are:
malloc() is called oncemalloc()/free() internal plumbing.You can see that, in these conditions, the following:
DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300));
is almost identical to
JsonBuffer* jsonBuffer = new StaticJsonBuffer<JSON_ARRAY_SIZE(300)>();
That's why I'm now recommending using DynamicJsonBuffer with an initial size.
As a conclusion, let's compare with ArduinoJson 5.0.4:.
JSON_ARRAY_SIZE(300) was 7208malloc() were made, for a total of 8256 bytesIf we include the overhead and the fragmentation, it's getting close to the 20K you're observing.
Thanks for the uber detailed response! much appreciated, i just felt like a learnt a lot.
Just thought of an extra question.
When allocating memory for the dynamic buffer, is there any way to know that you've got it right!
if my understanding is correct, using dynamic is recommended with the ESP as static might get too big to be allocated. but... say i calculate i need X.. based on using your macros. if i've done too little, then i assume the dynamic will just allocate more. what is your advice? is there a way to check?
my heap usage has now shrunk a lot, instead of 9k free, i have 21k! and the jsonbuffer is 5k. (there is other stuff there not just the array i mentioned)
You can call JsonBuffer::size() to know how much bytes have been allocated in the buffer.
So that's a very good way to check if the size you gave to the constructor is appropriate.
That works thanks.
I am working with the ArduinoJson library, I use DynamicJsonBuffer but when I write in the console several times it does not work. Can someone help me?
In addition I display the size of the buffer each time and it arrives at 0 and it does not work.
Thanks for replying.
I leave you my code
void loop() {
// put your main code here, to run repeatedly:
while (!Serial.available());
while (Serial.available())
{
String json = Serial.readString();
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(json);
Serial.println(jsonBuffer.size());
if (root.success())
{
parserJson(root); //Here, I take the json to work with him
}
else
{
return;
}
}
}
@Antuanett, I suggest that you let parseObject() read directly from Serial:
while (!Serial.available()) {}
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(Serial);
if (root.success()) {
parserJson(root); //Here, I take the json to work with him
}
Thank you, I had tried like that and it works
Most helpful comment
First of all, in version 5.0.5, I changed the storage type from
doubletofloatbecause the overhead wasn't worth it. In older versions, the memory consumption was nearly twice bigger on ESP8266.You can switch back to the
doublestorage by editing ArduinoJson/Internals/JsonFloat.hpp.Second remark, in version 5.0.6, I added the ability to set the initial size of
DynamicJsonBufferand changed the default from32to256.From there, I'm going to assume that you're using Arduino 5.0.6+ and kept the default
floatstorage.A
JsonArrayis just a linked list ofJsonVariant.So on an ESP8266:
floatis 4 bytes, so 300 of them is 1.2kJsonVariantis 8 bytes, so 300 of them is 2.4kJsonArrayhas an initial size of 8 bytes.So the total is 3608 bytes, which can be confirmed by:
Now, this is too big for the stack of the ESP8266, so using a
DynamicJsonBufferis appropriate.DynamicJsonBufferallocates blocks of memory usingmalloc(); each block is like aStaticJsonBuffer.As soon as it doesn't find enough memory in a block, it allocates a new one twice as big (like
std::vector).In our case, it will allocate four blocks:
Total: 3888 bytes
But the actual memory usage will be slightly higher due to the plumbing of
malloc()andfree().Also it introduces memory fragmentation that reduces the memory you can actually use.
Specifying the right initial size will allow
DynamicJsonBufferto make only onemalloc():The benefits are:
malloc()is called oncemalloc()/free()internal plumbing.You can see that, in these conditions, the following:
is almost identical to
That's why I'm now recommending using
DynamicJsonBufferwith an initial size.As a conclusion, let's compare with ArduinoJson 5.0.4:.
JSON_ARRAY_SIZE(300)was 7208malloc()were made, for a total of 8256 bytesIf we include the overhead and the fragmentation, it's getting close to the 20K you're observing.