JsonArray::remove() and JsonObject::remove() now release the memory of the variant

This commit is contained in:
Benoit Blanchon
2018-10-19 19:40:21 +02:00
parent ae089dcff7
commit d8d939660b
15 changed files with 174 additions and 37 deletions

View File

@ -8,6 +8,7 @@ HEAD
* Removed `JsonObject::is<T>(k)` and `JsonObject::set(k,v)` * Removed `JsonObject::is<T>(k)` and `JsonObject::set(k,v)`
* Replaced `T JsonArray::get<T>(i)` with `JsonVariant JsonArray::get(i)` * Replaced `T JsonArray::get<T>(i)` with `JsonVariant JsonArray::get(i)`
* Replaced `T JsonObject::get<T>(k)` with `JsonVariant JsonObject::get(k)` * Replaced `T JsonObject::get<T>(k)` with `JsonVariant JsonObject::get(k)`
* `JsonArray::remove()` and `JsonObject::remove()` now release the memory of the variant
v6.5.0-beta (2018-10-13) v6.5.0-beta (2018-10-13)
----------- -----------

View File

@ -13,10 +13,11 @@ namespace ARDUINOJSON_NAMESPACE {
inline JsonVariantData* arrayAdd(JsonArrayData* arr, MemoryPool* pool) { inline JsonVariantData* arrayAdd(JsonArrayData* arr, MemoryPool* pool) {
if (!arr) return 0; if (!arr) return 0;
Slot* slot = new (pool) Slot(); Slot* slot = pool->allocSlot();
if (!slot) return 0; if (!slot) return 0;
slot->next = 0; slot->next = 0;
slot->value.type = JSON_NULL;
if (arr->tail) { if (arr->tail) {
slot->prev = arr->tail; slot->prev = arr->tail;
@ -41,7 +42,7 @@ inline JsonVariantData* arrayGet(const JsonArrayData* arr, size_t index) {
return slot ? &slot->value : 0; return slot ? &slot->value : 0;
} }
inline void arrayRemove(JsonArrayData* arr, Slot* slot) { inline void arrayRemove(JsonArrayData* arr, Slot* slot, MemoryPool* pool) {
if (!arr || !slot) return; if (!arr || !slot) return;
if (slot->prev) if (slot->prev)
@ -52,10 +53,12 @@ inline void arrayRemove(JsonArrayData* arr, Slot* slot) {
slot->next->prev = slot->prev; slot->next->prev = slot->prev;
else else
arr->tail = slot->prev; arr->tail = slot->prev;
slotFree(slot, pool);
} }
inline void arrayRemove(JsonArrayData* arr, size_t index) { inline void arrayRemove(JsonArrayData* arr, size_t index, MemoryPool* pool) {
arrayRemove(arr, arrayGetSlot(arr, index)); arrayRemove(arr, arrayGetSlot(arr, index), pool);
} }
inline void arrayClear(JsonArrayData* arr) { inline void arrayClear(JsonArrayData* arr) {

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include "../Memory/MemoryPool.hpp"
#include "JsonVariantData.hpp" #include "JsonVariantData.hpp"
#include "SlotFunctions.hpp" #include "SlotFunctions.hpp"
@ -28,10 +29,11 @@ inline bool objectContainsKey(const JsonObjectData* obj, const TKey& key) {
template <typename TKey> template <typename TKey>
inline JsonVariantData* objectAdd(JsonObjectData* obj, TKey key, inline JsonVariantData* objectAdd(JsonObjectData* obj, TKey key,
MemoryPool* pool) { MemoryPool* pool) {
Slot* slot = new (pool) Slot(); Slot* slot = pool->allocSlot();
if (!slot) return 0; if (!slot) return 0;
slot->next = 0; slot->next = 0;
slot->value.type = JSON_NULL;
if (obj->tail) { if (obj->tail) {
slot->prev = obj->tail; slot->prev = obj->tail;
@ -74,7 +76,7 @@ inline void objectClear(JsonObjectData* obj) {
obj->tail = 0; obj->tail = 0;
} }
inline void objectRemove(JsonObjectData* obj, Slot* slot) { inline void objectRemove(JsonObjectData* obj, Slot* slot, MemoryPool* pool) {
if (!obj) return; if (!obj) return;
if (!slot) return; if (!slot) return;
if (slot->prev) if (slot->prev)
@ -85,6 +87,8 @@ inline void objectRemove(JsonObjectData* obj, Slot* slot) {
slot->next->prev = slot->prev; slot->next->prev = slot->prev;
else else
obj->tail = slot->prev; obj->tail = slot->prev;
slotFree(slot, pool);
} }
inline size_t objectSize(const JsonObjectData* obj) { inline size_t objectSize(const JsonObjectData* obj) {

View File

@ -4,12 +4,11 @@
#pragma once #pragma once
#include "../Memory/AllocableInMemoryPool.hpp"
#include "JsonVariantData.hpp" #include "JsonVariantData.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
struct Slot : AllocableInMemoryPool { struct Slot {
JsonVariantData value; JsonVariantData value;
struct Slot* next; struct Slot* next;
struct Slot* prev; struct Slot* prev;

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include "../Memory/MemoryPool.hpp"
#include "../Strings/StringTypes.hpp" #include "../Strings/StringTypes.hpp"
#include "JsonVariantData.hpp" #include "JsonVariantData.hpp"
#include "Slot.hpp" #include "Slot.hpp"
@ -56,4 +57,15 @@ inline size_t slotSize(const Slot* slot) {
} }
return n; return n;
} }
inline void slotFree(Slot* slot, MemoryPool* pool) {
const JsonVariantData& v = slot->value;
if (v.type == JSON_ARRAY || v.type == JSON_OBJECT) {
for (Slot* s = v.content.asObject.head; s; s = s->next) {
slotFree(s, pool);
}
}
pool->freeSlot(slot);
}
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -197,12 +197,12 @@ class JsonArray : public JsonArrayProxy<JsonArrayData>, public Visitable {
// Removes element at specified position. // Removes element at specified position.
FORCE_INLINE void remove(iterator it) const { FORCE_INLINE void remove(iterator it) const {
arrayRemove(_data, it.internal()); arrayRemove(_data, it.internal(), _memoryPool);
} }
// Removes element at specified index. // Removes element at specified index.
FORCE_INLINE void remove(size_t index) const { FORCE_INLINE void remove(size_t index) const {
arrayRemove(_data, index); arrayRemove(_data, index, _memoryPool);
} }
template <typename Visitor> template <typename Visitor>

View File

@ -227,7 +227,7 @@ class JsonObject : public JsonObjectProxy<JsonObjectData>, public Visitable {
} }
FORCE_INLINE void remove(iterator it) const { FORCE_INLINE void remove(iterator it) const {
objectRemove(_data, it.internal()); objectRemove(_data, it.internal(), _memoryPool);
} }
// Removes the specified key and the associated value. // Removes the specified key and the associated value.
@ -278,7 +278,7 @@ class JsonObject : public JsonObjectProxy<JsonObjectData>, public Visitable {
template <typename TStringRef> template <typename TStringRef>
FORCE_INLINE void remove_impl(TStringRef key) const { FORCE_INLINE void remove_impl(TStringRef key) const {
objectRemove(_data, objectFindSlot(_data, key)); objectRemove(_data, objectFindSlot(_data, key), _memoryPool);
} }
MemoryPool* _memoryPool; MemoryPool* _memoryPool;

View File

@ -1,19 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#include "MemoryPool.hpp"
namespace ARDUINOJSON_NAMESPACE {
class AllocableInMemoryPool {
public:
void *operator new(size_t n, MemoryPool *memoryPool) NOEXCEPT {
return memoryPool->alloc(n);
}
void operator delete(void *, MemoryPool *)NOEXCEPT {}
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -58,7 +58,7 @@ class DynamicMemoryPoolBase : public MemoryPool {
} }
// Gets the number of bytes occupied in the memoryPool // Gets the number of bytes occupied in the memoryPool
size_t size() const { virtual size_t allocated_bytes() const {
size_t total = 0; size_t total = 0;
for (const Block* b = _head; b; b = b->next) total += b->size; for (const Block* b = _head; b; b = b->next) total += b->size;
return total; return total;

View File

@ -9,6 +9,7 @@
#include <string.h> #include <string.h>
#include "../Configuration.hpp" #include "../Configuration.hpp"
#include "../Data/Slot.hpp"
#include "../Polyfills/attributes.hpp" #include "../Polyfills/attributes.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
@ -23,12 +24,36 @@ class MemoryPool {
virtual char *realloc(char *oldPtr, size_t oldSize, size_t newSize) = 0; virtual char *realloc(char *oldPtr, size_t oldSize, size_t newSize) = 0;
Slot *allocSlot() {
if (_freeSlots) {
Slot *s = _freeSlots;
_freeSlots = s->next;
return s;
}
return reinterpret_cast<Slot *>(alloc(sizeof(Slot)));
}
void freeSlot(Slot *slot) {
slot->next = _freeSlots;
_freeSlots = slot;
}
size_t size() const {
size_t result = allocated_bytes();
for (Slot *s = _freeSlots; s; s = s->next) result -= sizeof(Slot);
return result;
}
protected: protected:
MemoryPool() : _freeSlots(0) {}
// CAUTION: NO VIRTUAL DESTRUCTOR! // CAUTION: NO VIRTUAL DESTRUCTOR!
// If we add a virtual constructor the Arduino compiler will add malloc() // If we add a virtual constructor the Arduino compiler will add malloc()
// and free() to the binary, adding 706 useless bytes. // and free() to the binary, adding 706 useless bytes.
~MemoryPool() {} ~MemoryPool() {}
virtual size_t allocated_bytes() const = 0;
// Preserve aligment if necessary // Preserve aligment if necessary
static FORCE_INLINE size_t round_size_up(size_t bytes) { static FORCE_INLINE size_t round_size_up(size_t bytes) {
#if ARDUINOJSON_ENABLE_ALIGNMENT #if ARDUINOJSON_ENABLE_ALIGNMENT
@ -38,5 +63,8 @@ class MemoryPool {
return bytes; return bytes;
#endif #endif
} }
private:
Slot *_freeSlots;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -18,11 +18,6 @@ class StaticMemoryPoolBase : public MemoryPool {
return _capacity; return _capacity;
} }
// Gets the current usage of the memoryPool in bytes
size_t size() const {
return _size;
}
// Allocates the specified amount of bytes in the memoryPool // Allocates the specified amount of bytes in the memoryPool
virtual char* alloc(size_t bytes) { virtual char* alloc(size_t bytes) {
alignNextAlloc(); alignNextAlloc();
@ -53,6 +48,11 @@ class StaticMemoryPoolBase : public MemoryPool {
~StaticMemoryPoolBase() {} ~StaticMemoryPoolBase() {}
// Gets the current usage of the memoryPool in bytes
virtual size_t allocated_bytes() const {
return _size;
}
private: private:
void alignNextAlloc() { void alignNextAlloc() {
_size = round_size_up(_size); _size = round_size_up(_size);

View File

@ -4,6 +4,7 @@
add_executable(DynamicMemoryPoolTests add_executable(DynamicMemoryPoolTests
alloc.cpp alloc.cpp
allocSlot.cpp
no_memory.cpp no_memory.cpp
size.cpp size.cpp
startString.cpp startString.cpp

View File

@ -0,0 +1,27 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("DynamicMemoryPool::allocSlot()") {
DynamicMemoryPool memoryPool;
SECTION("Returns different pointer") {
Slot* s1 = memoryPool.allocSlot();
Slot* s2 = memoryPool.allocSlot();
REQUIRE(s1 != s2);
}
SECTION("Returns same pointer after freeSlot()") {
Slot* s1 = memoryPool.allocSlot();
memoryPool.freeSlot(s1);
Slot* s2 = memoryPool.allocSlot();
REQUIRE(s1 == s2);
}
}

View File

@ -26,4 +26,23 @@ TEST_CASE("DynamicMemoryPool::size()") {
memoryPool.clear(); memoryPool.clear();
REQUIRE(0 == memoryPool.size()); REQUIRE(0 == memoryPool.size());
} }
SECTION("Increases after allocSlot()") {
memoryPool.allocSlot();
REQUIRE(sizeof(Slot) == memoryPool.size());
memoryPool.allocSlot();
REQUIRE(2 * sizeof(Slot) == memoryPool.size());
}
SECTION("Decreases after freeSlot()") {
Slot* s1 = memoryPool.allocSlot();
Slot* s2 = memoryPool.allocSlot();
memoryPool.freeSlot(s1);
REQUIRE(sizeof(Slot) == memoryPool.size());
memoryPool.freeSlot(s2);
REQUIRE(0 == memoryPool.size());
}
} }

View File

@ -92,4 +92,66 @@ TEST_CASE("DynamicJsonDocument") {
REQUIRE(json == "{\"hello\":\"world\"}"); REQUIRE(json == "{\"hello\":\"world\"}");
REQUIRE(ddoc.nestingLimit == 42); REQUIRE(ddoc.nestingLimit == 42);
} }
SECTION("memoryUsage()") {
typedef ARDUINOJSON_NAMESPACE::Slot Slot;
SECTION("Increases after adding value to array") {
JsonArray arr = doc.to<JsonArray>();
arr.add(42);
REQUIRE(sizeof(Slot) == doc.memoryUsage());
arr.add(43);
REQUIRE(2 * sizeof(Slot) == doc.memoryUsage());
}
SECTION("Decreases after remove value from array") {
JsonArray arr = doc.to<JsonArray>();
arr.add(42);
arr.add(43);
arr.remove(1);
REQUIRE(sizeof(Slot) == doc.memoryUsage());
arr.remove(0);
REQUIRE(0 == doc.memoryUsage());
}
SECTION("Increases after adding value to object") {
JsonObject obj = doc.to<JsonObject>();
obj["a"] = 1;
REQUIRE(sizeof(Slot) == doc.memoryUsage());
obj["b"] = 2;
REQUIRE(2 * sizeof(Slot) == doc.memoryUsage());
}
SECTION("Decreases after removing value from object") {
JsonObject obj = doc.to<JsonObject>();
obj["a"] = 1;
obj["b"] = 2;
obj.remove("a");
REQUIRE(sizeof(Slot) == doc.memoryUsage());
obj.remove("b");
REQUIRE(0 == doc.memoryUsage());
}
SECTION("Decreases after removing nested object from array") {
JsonArray arr = doc.to<JsonArray>();
JsonObject obj = arr.createNestedObject();
obj["hello"] = "world";
arr.remove(0);
REQUIRE(0 == doc.memoryUsage());
}
SECTION("Decreases after removing nested array from object") {
JsonObject obj = doc.to<JsonObject>();
JsonArray arr = obj.createNestedArray("hello");
arr.add("world");
obj.remove("hello");
REQUIRE(0 == doc.memoryUsage());
}
}
} }