MemoryPool calls the Allocator directly

This commit is contained in:
Benoit Blanchon
2023-03-20 12:28:34 +01:00
parent 540901e219
commit 5faa3df43f
10 changed files with 131 additions and 86 deletions

View File

@ -0,0 +1,28 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2023, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Memory/Allocator.hpp>
struct FailingAllocator : ArduinoJson::Allocator {
static FailingAllocator* instance() {
static FailingAllocator allocator;
return &allocator;
}
private:
FailingAllocator() = default;
~FailingAllocator() = default;
void* allocate(size_t) override {
return nullptr;
}
void deallocate(void*) override {}
void* reallocate(void*, size_t) override {
return nullptr;
}
};

View File

@ -8,10 +8,8 @@
using namespace ArduinoJson::detail; using namespace ArduinoJson::detail;
TEST_CASE("StringCopier") { TEST_CASE("StringCopier") {
char buffer[4096];
SECTION("Works when buffer is big enough") { SECTION("Works when buffer is big enough") {
MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(5))); MemoryPool pool(addPadding(JSON_STRING_SIZE(5)));
StringCopier str(&pool); StringCopier str(&pool);
str.startString(); str.startString();
@ -23,7 +21,7 @@ TEST_CASE("StringCopier") {
} }
SECTION("Returns null when too small") { SECTION("Returns null when too small") {
MemoryPool pool(buffer, sizeof(void*)); MemoryPool pool(sizeof(void*));
StringCopier str(&pool); StringCopier str(&pool);
str.startString(); str.startString();
@ -34,7 +32,7 @@ TEST_CASE("StringCopier") {
} }
SECTION("Increases size of memory pool") { SECTION("Increases size of memory pool") {
MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(6))); MemoryPool pool(addPadding(JSON_STRING_SIZE(6)));
StringCopier str(&pool); StringCopier str(&pool);
str.startString(); str.startString();
@ -45,7 +43,7 @@ TEST_CASE("StringCopier") {
} }
SECTION("Works when memory pool is 0 bytes") { SECTION("Works when memory pool is 0 bytes") {
MemoryPool pool(buffer, 0); MemoryPool pool(0);
StringCopier str(&pool); StringCopier str(&pool);
str.startString(); str.startString();
@ -62,8 +60,7 @@ static const char* addStringToPool(MemoryPool& pool, const char* s) {
} }
TEST_CASE("StringCopier::save() deduplicates strings") { TEST_CASE("StringCopier::save() deduplicates strings") {
char buffer[4096]; MemoryPool pool(4096);
MemoryPool pool(buffer, 4096);
SECTION("Basic") { SECTION("Basic") {
const char* s1 = addStringToPool(pool, "hello"); const char* s1 = addStringToPool(pool, "hello");

View File

@ -5,13 +5,13 @@
#include <ArduinoJson/Memory/MemoryPool.hpp> #include <ArduinoJson/Memory/MemoryPool.hpp>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail; using namespace ArduinoJson::detail;
TEST_CASE("MemoryPool::allocVariant()") { TEST_CASE("MemoryPool::allocVariant()") {
char buffer[4096];
SECTION("Returns different pointer") { SECTION("Returns different pointer") {
MemoryPool pool(buffer, sizeof(buffer)); MemoryPool pool(4096);
VariantSlot* s1 = pool.allocVariant(); VariantSlot* s1 = pool.allocVariant();
REQUIRE(s1 != 0); REQUIRE(s1 != 0);
@ -22,26 +22,26 @@ TEST_CASE("MemoryPool::allocVariant()") {
} }
SECTION("Returns aligned pointers") { SECTION("Returns aligned pointers") {
MemoryPool pool(buffer, sizeof(buffer)); MemoryPool pool(4096);
REQUIRE(isAligned(pool.allocVariant())); REQUIRE(isAligned(pool.allocVariant()));
REQUIRE(isAligned(pool.allocVariant())); REQUIRE(isAligned(pool.allocVariant()));
} }
SECTION("Returns zero if capacity is 0") { SECTION("Returns zero if capacity is 0") {
MemoryPool pool(buffer, 0); MemoryPool pool(0);
REQUIRE(pool.allocVariant() == 0); REQUIRE(pool.allocVariant() == 0);
} }
SECTION("Returns zero if buffer is null") { SECTION("Returns zero if buffer is null") {
MemoryPool pool(0, sizeof(buffer)); MemoryPool pool(4096, FailingAllocator::instance());
REQUIRE(pool.allocVariant() == 0); REQUIRE(pool.allocVariant() == 0);
} }
SECTION("Returns zero if capacity is insufficient") { SECTION("Returns zero if capacity is insufficient") {
MemoryPool pool(buffer, sizeof(VariantSlot)); MemoryPool pool(sizeof(VariantSlot));
pool.allocVariant(); pool.allocVariant();

View File

@ -11,8 +11,7 @@ using namespace ArduinoJson::detail;
static const size_t poolCapacity = 512; static const size_t poolCapacity = 512;
TEST_CASE("MemoryPool::clear()") { TEST_CASE("MemoryPool::clear()") {
char buffer[poolCapacity]; MemoryPool pool(poolCapacity);
MemoryPool pool(buffer, sizeof(buffer));
SECTION("Discards allocated variants") { SECTION("Discards allocated variants") {
pool.allocVariant(); pool.allocVariant();

View File

@ -6,6 +6,8 @@
#include <ArduinoJson/Strings/StringAdapters.hpp> #include <ArduinoJson/Strings/StringAdapters.hpp>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail; using namespace ArduinoJson::detail;
static const char* saveString(MemoryPool& pool, const char* s) { static const char* saveString(MemoryPool& pool, const char* s) {
@ -17,8 +19,7 @@ static const char* saveString(MemoryPool& pool, const char* s, size_t n) {
} }
TEST_CASE("MemoryPool::saveString()") { TEST_CASE("MemoryPool::saveString()") {
char buffer[32]; MemoryPool pool(32);
MemoryPool pool(buffer, 32);
SECTION("Duplicates different strings") { SECTION("Duplicates different strings") {
const char* a = saveString(pool, "hello"); const char* a = saveString(pool, "hello");
@ -72,12 +73,12 @@ TEST_CASE("MemoryPool::saveString()") {
} }
SECTION("Returns NULL when buffer is NULL") { SECTION("Returns NULL when buffer is NULL") {
MemoryPool pool2(0, 32); MemoryPool pool2(32, FailingAllocator::instance());
REQUIRE(0 == saveString(pool2, "a")); REQUIRE(0 == saveString(pool2, "a"));
} }
SECTION("Returns NULL when capacity is 0") { SECTION("Returns NULL when capacity is 0") {
MemoryPool pool2(buffer, 0); MemoryPool pool2(0);
REQUIRE(0 == saveString(pool2, "a")); REQUIRE(0 == saveString(pool2, "a"));
} }

View File

@ -8,22 +8,20 @@
using namespace ArduinoJson::detail; using namespace ArduinoJson::detail;
TEST_CASE("MemoryPool::capacity()") { TEST_CASE("MemoryPool::capacity()") {
char buffer[4096];
const size_t capacity = 64; const size_t capacity = 64;
MemoryPool pool(buffer, capacity); MemoryPool pool(capacity);
REQUIRE(capacity == pool.capacity()); REQUIRE(capacity == pool.capacity());
} }
TEST_CASE("MemoryPool::size()") { TEST_CASE("MemoryPool::size()") {
char buffer[4096]; MemoryPool pool(4096);
MemoryPool pool(buffer, sizeof(buffer));
SECTION("Initial size is 0") { SECTION("Initial size is 0") {
REQUIRE(0 == pool.size()); REQUIRE(0 == pool.size());
} }
SECTION("Doesn't grow when memory pool is full") { SECTION("Doesn't grow when memory pool is full") {
const size_t variantCount = sizeof(buffer) / sizeof(VariantSlot); const size_t variantCount = pool.capacity() / sizeof(VariantSlot);
for (size_t i = 0; i < variantCount; i++) for (size_t i = 0; i < variantCount; i++)
pool.allocVariant(); pool.allocVariant();

View File

@ -10,8 +10,7 @@
using namespace ArduinoJson::detail; using namespace ArduinoJson::detail;
static void testCodepoint(uint32_t codepoint, std::string expected) { static void testCodepoint(uint32_t codepoint, std::string expected) {
char buffer[4096]; MemoryPool pool(4096);
MemoryPool pool(buffer, 4096);
StringCopier str(&pool); StringCopier str(&pool);
str.startString(); str.startString();

View File

@ -9,6 +9,7 @@
#include <ArduinoJson/Memory/MemoryPool.hpp> #include <ArduinoJson/Memory/MemoryPool.hpp>
#include <ArduinoJson/Object/JsonObject.hpp> #include <ArduinoJson/Object/JsonObject.hpp>
#include <ArduinoJson/Object/MemberProxy.hpp> #include <ArduinoJson/Object/MemberProxy.hpp>
#include <ArduinoJson/Polyfills/utility.hpp>
#include <ArduinoJson/Strings/StoragePolicy.hpp> #include <ArduinoJson/Strings/StoragePolicy.hpp>
#include <ArduinoJson/Variant/JsonVariantConst.hpp> #include <ArduinoJson/Variant/JsonVariantConst.hpp>
#include <ArduinoJson/Variant/VariantTo.hpp> #include <ArduinoJson/Variant/VariantTo.hpp>
@ -23,16 +24,16 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
public: public:
explicit JsonDocument(size_t capa, explicit JsonDocument(size_t capa,
Allocator* alloc = detail::DefaultAllocator::instance()) Allocator* alloc = detail::DefaultAllocator::instance())
: _allocator(alloc), _pool(allocPool(capa)) {} : _pool(capa, alloc) {}
// Copy-constructor // Copy-constructor
JsonDocument(const JsonDocument& src) JsonDocument(const JsonDocument& src)
: JsonDocument(src.capacity(), src._allocator) { : JsonDocument(src.capacity(), src.allocator()) {
set(src); set(src);
} }
// Move-constructor // Move-constructor
JsonDocument(JsonDocument&& src) : _allocator(src._allocator), _pool(0, 0) { JsonDocument(JsonDocument&& src) : _pool(0, src.allocator()) {
// TODO: use the copy and swap idiom // TODO: use the copy and swap idiom
moveAssignFrom(src); moveAssignFrom(src);
} }
@ -57,10 +58,6 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
set(src); set(src);
} }
~JsonDocument() {
freePool();
}
JsonDocument& operator=(const JsonDocument& src) { JsonDocument& operator=(const JsonDocument& src) {
// TODO: use the copy and swap idiom // TODO: use the copy and swap idiom
copyAssignFrom(src); copyAssignFrom(src);
@ -77,26 +74,19 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
JsonDocument& operator=(const T& src) { JsonDocument& operator=(const T& src) {
size_t requiredSize = src.memoryUsage(); size_t requiredSize = src.memoryUsage();
if (requiredSize > capacity()) if (requiredSize > capacity())
reallocPool(requiredSize); _pool.reallocPool(requiredSize);
set(src); set(src);
return *this; return *this;
} }
Allocator* allocator() const {
return _pool.allocator();
}
// Reduces the capacity of the memory pool to match the current usage. // Reduces the capacity of the memory pool to match the current usage.
// https://arduinojson.org/v6/api/JsonDocument/shrinktofit/ // https://arduinojson.org/v6/api/JsonDocument/shrinktofit/
void shrinkToFit() { void shrinkToFit() {
ptrdiff_t bytes_reclaimed = _pool.squash(); _pool.shrinkToFit(_data);
if (bytes_reclaimed == 0)
return;
void* old_ptr = _pool.buffer();
void* new_ptr = _allocator->reallocate(old_ptr, _pool.capacity());
ptrdiff_t ptr_offset =
static_cast<char*>(new_ptr) - static_cast<char*>(old_ptr);
_pool.movePointers(ptr_offset);
_data.movePointers(ptr_offset, ptr_offset - bytes_reclaimed);
} }
// Reclaims the memory leaked when removing and replacing values. // Reclaims the memory leaked when removing and replacing values.
@ -365,10 +355,6 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
} }
private: private:
void replacePool(detail::MemoryPool pool) {
_pool = pool;
}
JsonVariant getVariant() { JsonVariant getVariant() {
return JsonVariant(&_pool, &_data); return JsonVariant(&_pool, &_data);
} }
@ -377,36 +363,15 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
return JsonVariantConst(&_data); return JsonVariantConst(&_data);
} }
detail::MemoryPool allocPool(size_t requiredSize) {
size_t capa = detail::addPadding(requiredSize);
return {reinterpret_cast<char*>(_allocator->allocate(capa)), capa};
}
void reallocPool(size_t requiredSize) {
size_t capa = detail::addPadding(requiredSize);
if (capa == _pool.capacity())
return;
freePool();
replacePool(allocPool(detail::addPadding(requiredSize)));
}
void freePool() {
auto p = getPool()->buffer();
if (p)
_allocator->deallocate(p);
}
void copyAssignFrom(const JsonDocument& src) { void copyAssignFrom(const JsonDocument& src) {
reallocPool(src.capacity()); _pool.reallocPool(src.capacity());
set(src); set(src);
} }
void moveAssignFrom(JsonDocument& src) { void moveAssignFrom(JsonDocument& src) {
freePool();
_data = src._data; _data = src._data;
_pool = src._pool;
src._data.setNull(); src._data.setNull();
src._pool = {0, 0}; _pool = move(src._pool);
} }
detail::MemoryPool* getPool() { detail::MemoryPool* getPool() {
@ -425,7 +390,6 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
return &_data; return &_data;
} }
Allocator* _allocator;
detail::MemoryPool _pool; detail::MemoryPool _pool;
detail::VariantData _data; detail::VariantData _data;
}; };

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <ArduinoJson/Memory/Alignment.hpp> #include <ArduinoJson/Memory/Alignment.hpp>
#include <ArduinoJson/Memory/Allocator.hpp>
#include <ArduinoJson/Polyfills/assert.hpp> #include <ArduinoJson/Polyfills/assert.hpp>
#include <ArduinoJson/Polyfills/mpl/max.hpp> #include <ArduinoJson/Polyfills/mpl/max.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp> #include <ArduinoJson/Strings/StringAdapters.hpp>
@ -36,15 +37,42 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class MemoryPool { class MemoryPool {
public: public:
MemoryPool(char* buf, size_t capa) MemoryPool(size_t capa, Allocator* allocator = DefaultAllocator::instance())
: _begin(buf), : _allocator(allocator), _overflowed(false) {
_left(buf), allocPool(addPadding(capa));
_right(buf ? buf + capa : 0), }
_end(buf ? buf + capa : 0),
_overflowed(false) { ~MemoryPool() {
ARDUINOJSON_ASSERT(isAligned(_begin)); if (_begin)
ARDUINOJSON_ASSERT(isAligned(_right)); _allocator->deallocate(_begin);
ARDUINOJSON_ASSERT(isAligned(_end)); }
MemoryPool(const MemoryPool&) = delete;
MemoryPool& operator=(const MemoryPool& src) = delete;
MemoryPool& operator=(MemoryPool&& src) {
if (_begin)
_allocator->deallocate(_begin);
_allocator = src._allocator;
_begin = src._begin;
_end = src._end;
_left = src._left;
_right = src._right;
_overflowed = src._overflowed;
src._begin = src._end = src._left = src._right = nullptr;
return *this;
}
Allocator* allocator() const {
return _allocator;
}
void reallocPool(size_t requiredSize) {
size_t capa = addPadding(requiredSize);
if (capa == capacity())
return;
_allocator->deallocate(_begin);
allocPool(requiredSize);
} }
void* buffer() { void* buffer() {
@ -132,6 +160,23 @@ class MemoryPool {
return p; return p;
} }
void shrinkToFit(VariantData& variant) {
ptrdiff_t bytes_reclaimed = squash();
if (bytes_reclaimed == 0)
return;
void* old_ptr = _begin;
void* new_ptr = _allocator->reallocate(old_ptr, capacity());
ptrdiff_t ptr_offset =
static_cast<char*>(new_ptr) - static_cast<char*>(old_ptr);
movePointers(ptr_offset);
reinterpret_cast<VariantSlot&>(variant).movePointers(
ptr_offset, ptr_offset - bytes_reclaimed);
}
private:
// Squash the free space between strings and variants // Squash the free space between strings and variants
// //
// _begin _end // _begin _end
@ -166,7 +211,6 @@ class MemoryPool {
_end += offset; _end += offset;
} }
private:
void checkInvariants() { void checkInvariants() {
ARDUINOJSON_ASSERT(_begin <= _left); ARDUINOJSON_ASSERT(_begin <= _left);
ARDUINOJSON_ASSERT(_left <= _right); ARDUINOJSON_ASSERT(_left <= _right);
@ -215,6 +259,16 @@ class MemoryPool {
return _right; return _right;
} }
void allocPool(size_t capa) {
auto buf = capa ? reinterpret_cast<char*>(_allocator->allocate(capa)) : 0;
_begin = _left = buf;
_end = _right = buf ? buf + capa : 0;
ARDUINOJSON_ASSERT(isAligned(_begin));
ARDUINOJSON_ASSERT(isAligned(_right));
ARDUINOJSON_ASSERT(isAligned(_end));
}
Allocator* _allocator;
char *_begin, *_left, *_right, *_end; char *_begin, *_left, *_right, *_end;
bool _overflowed; bool _overflowed;
}; };

View File

@ -13,4 +13,9 @@ T&& forward(typename remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t); return static_cast<T&&>(t);
} }
template <class T>
typename remove_reference<T>::type&& move(T&& t) {
return static_cast<typename remove_reference<T>::type&&>(t);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE