Store the strings in the heap

This commit is contained in:
Benoit Blanchon
2023-04-17 10:42:08 +02:00
parent 7c0fa7c276
commit d8f3058efa
27 changed files with 434 additions and 377 deletions
@@ -70,7 +70,7 @@ class CollectionData {
return _head;
}
void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance);
void movePointers(ptrdiff_t variantDistance);
private:
VariantSlot* getSlot(size_t index) const;
@@ -168,7 +168,7 @@ inline size_t CollectionData::memoryUsage() const {
for (VariantSlot* s = _head; s; s = s->next()) {
total += sizeof(VariantSlot) + s->data()->memoryUsage();
if (s->ownsKey())
total += strlen(s->key()) + 1;
total += sizeofString(strlen(s->key()));
}
return total;
}
@@ -186,12 +186,11 @@ inline void movePointer(T*& p, ptrdiff_t offset) {
ARDUINOJSON_ASSERT(isAligned(p));
}
inline void CollectionData::movePointers(ptrdiff_t stringDistance,
ptrdiff_t variantDistance) {
inline void CollectionData::movePointers(ptrdiff_t variantDistance) {
movePointer(_head, variantDistance);
movePointer(_tail, variantDistance);
for (VariantSlot* slot = _head; slot; slot = slot->next())
slot->movePointers(stringDistance, variantDistance);
slot->movePointers(variantDistance);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE
@@ -277,7 +277,6 @@ class JsonDeserializer {
VariantData* variant = object.getMember(adaptString(key.c_str()));
if (!variant) {
// Save key in memory pool.
// This MUST be done before adding the slot.
key = _stringStorage.save();
// Allocate slot in object
+72 -49
View File
@@ -25,9 +25,15 @@ constexpr size_t sizeofObject(size_t n) {
return n * sizeof(VariantSlot);
}
struct StringNode {
struct StringNode* next;
uint16_t length;
char data[1];
};
// Returns the size (in bytes) of an string with n characters.
constexpr size_t sizeofString(size_t n) {
return n + 1;
return n + 1 + offsetof(StringNode, data);
}
// _begin _end
@@ -46,6 +52,7 @@ class MemoryPool {
}
~MemoryPool() {
deallocAllStrings();
deallocPool();
}
@@ -53,6 +60,7 @@ class MemoryPool {
MemoryPool& operator=(const MemoryPool& src) = delete;
MemoryPool& operator=(MemoryPool&& src) {
deallocAllStrings();
deallocPool();
_allocator = src._allocator;
_begin = src._begin;
@@ -61,6 +69,8 @@ class MemoryPool {
_right = src._right;
_overflowed = src._overflowed;
src._begin = src._end = src._left = src._right = nullptr;
_strings = src._strings;
src._strings = nullptr;
return *this;
}
@@ -87,7 +97,10 @@ class MemoryPool {
}
size_t size() const {
return size_t(_left - _begin + _end - _right);
size_t total = size_t(_left - _begin + _end - _right);
for (auto node = _strings; node; node = node->next)
total += sizeofString(node->length);
return total;
}
bool overflowed() const {
@@ -103,45 +116,71 @@ class MemoryPool {
if (str.isNull())
return 0;
const char* existingCopy = findString(str);
if (existingCopy)
return existingCopy;
auto node = findString(str);
if (node) {
return node->data;
}
size_t n = str.size();
char* newCopy = allocString(n + 1);
if (newCopy) {
stringGetChars(str, newCopy, n);
newCopy[n] = 0; // force null-terminator
node = allocString(n);
if (!node)
return nullptr;
stringGetChars(str, node->data, n);
node->data[n] = 0; // force NUL terminator
addStringToList(node);
return node->data;
}
void addStringToList(StringNode* node) {
ARDUINOJSON_ASSERT(node != nullptr);
node->next = _strings;
_strings = node;
}
template <typename TAdaptedString>
StringNode* findString(const TAdaptedString& str) const {
for (auto node = _strings; node; node = node->next) {
if (stringEquals(str, adaptString(node->data, node->length)))
return node;
}
return newCopy;
return nullptr;
}
void getFreeZone(char** zoneStart, size_t* zoneSize) const {
*zoneStart = _left;
*zoneSize = size_t(_right - _left);
StringNode* allocString(size_t length) {
auto node = reinterpret_cast<StringNode*>(
_allocator->allocate(sizeofString(length)));
if (node) {
node->length = uint16_t(length);
} else {
_overflowed = true;
}
return node;
}
const char* saveStringFromFreeZone(size_t len) {
const char* dup = findString(adaptString(_left, len));
if (dup)
return dup;
const char* str = _left;
_left += len;
*_left++ = 0;
checkInvariants();
return str;
StringNode* reallocString(StringNode* node, size_t length) {
ARDUINOJSON_ASSERT(node != nullptr);
auto newNode = reinterpret_cast<StringNode*>(
_allocator->reallocate(node, sizeofString(length)));
if (newNode) {
newNode->length = uint16_t(length);
} else {
_overflowed = true;
_allocator->deallocate(node);
}
return newNode;
}
void markAsOverflowed() {
_overflowed = true;
void deallocString(StringNode* node) {
_allocator->deallocate(node);
}
void clear() {
_left = _begin;
_right = _end;
_overflowed = false;
deallocAllStrings();
}
bool canAlloc(size_t bytes) const {
@@ -169,8 +208,8 @@ class MemoryPool {
static_cast<char*>(new_ptr) - static_cast<char*>(old_ptr);
movePointers(ptr_offset);
reinterpret_cast<VariantSlot&>(variant).movePointers(
ptr_offset, ptr_offset - bytes_reclaimed);
reinterpret_cast<VariantSlot&>(variant).movePointers(ptr_offset -
bytes_reclaimed);
}
private:
@@ -215,29 +254,12 @@ class MemoryPool {
ARDUINOJSON_ASSERT(isAligned(_right));
}
template <typename TAdaptedString>
const char* findString(const TAdaptedString& str) const {
size_t n = str.size();
for (char* next = _begin; next + n < _left; ++next) {
if (next[n] == '\0' && stringEquals(str, adaptString(next, n)))
return next;
// jump to next terminator
while (*next)
++next;
void deallocAllStrings() {
while (_strings) {
auto node = _strings;
_strings = node->next;
deallocString(node);
}
return 0;
}
char* allocString(size_t n) {
if (!canAlloc(n)) {
_overflowed = true;
return 0;
}
char* s = _left;
_left += n;
checkInvariants();
return s;
}
template <typename T>
@@ -271,6 +293,7 @@ class MemoryPool {
Allocator* _allocator;
char *_begin, *_left, *_right, *_end;
bool _overflowed;
StringNode* _strings = nullptr;
};
template <typename TAdaptedString, typename TCallback>
@@ -494,7 +494,6 @@ class MsgPackDeserializer {
ARDUINOJSON_ASSERT(object != 0);
// Save key in memory pool.
// This MUST be done before adding the slot.
key = _stringStorage.save();
VariantSlot* slot = object->addSlot(_pool);
+29 -24
View File
@@ -10,20 +10,31 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class StringCopier {
public:
static const size_t initialCapacity = 31;
StringCopier(MemoryPool* pool) : _pool(pool) {}
~StringCopier() {
if (_node)
_pool->deallocString(_node);
}
void startString() {
_pool->getFreeZone(&_ptr, &_capacity);
_size = 0;
if (_capacity == 0)
_pool->markAsOverflowed();
if (!_node)
_node = _pool->allocString(initialCapacity);
}
JsonString save() {
ARDUINOJSON_ASSERT(_ptr);
ARDUINOJSON_ASSERT(_size < _capacity); // needs room for the terminator
return JsonString(_pool->saveStringFromFreeZone(_size), _size,
JsonString::Copied);
ARDUINOJSON_ASSERT(_node != nullptr);
_node->data[_size] = 0;
StringNode* node = _pool->findString(adaptString(_node->data, _size));
if (!node) {
node = _pool->reallocString(_node, _size);
_pool->addStringToList(node);
_node = nullptr; // next time we need a new string
}
return JsonString(node->data, node->length, JsonString::Copied);
}
void append(const char* s) {
@@ -32,19 +43,19 @@ class StringCopier {
}
void append(const char* s, size_t n) {
while (n-- > 0)
while (n-- > 0) // TODO: memcpy
append(*s++);
}
void append(char c) {
if (_size + 1 < _capacity)
_ptr[_size++] = c;
else
_pool->markAsOverflowed();
if (_node && _size == _node->length)
_node = _pool->reallocString(_node, _size * 2U + 1);
if (_node)
_node->data[_size++] = c;
}
bool isValid() const {
return !_pool->overflowed();
return _node != nullptr;
}
size_t size() const {
@@ -52,21 +63,15 @@ class StringCopier {
}
JsonString str() const {
ARDUINOJSON_ASSERT(_ptr);
ARDUINOJSON_ASSERT(_size < _capacity);
_ptr[_size] = 0;
return JsonString(_ptr, _size, JsonString::Copied);
ARDUINOJSON_ASSERT(_node != nullptr);
_node->data[_size] = 0;
return JsonString(_node->data, _size, JsonString::Copied);
}
private:
MemoryPool* _pool;
// These fields aren't initialized by the constructor but startString()
//
// NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.UninitializedObject)
char* _ptr;
// NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.UninitializedObject)
size_t _size, _capacity;
StringNode* _node = nullptr;
size_t _size = 0;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE
+13 -21
View File
@@ -5,6 +5,7 @@
#pragma once
#include <ArduinoJson/Json/JsonSerializer.hpp>
#include <ArduinoJson/StringStorage/StringCopier.hpp>
#include <ArduinoJson/Variant/JsonVariantConst.hpp>
#include <ArduinoJson/Variant/VariantFunctions.hpp>
@@ -205,43 +206,35 @@ struct Converter<decltype(nullptr)> : private detail::VariantAttorney {
namespace detail {
class MemoryPoolPrint : public Print {
public:
MemoryPoolPrint(MemoryPool* pool) : _pool(pool), _size(0) {
pool->getFreeZone(&_string, &_capacity);
MemoryPoolPrint(MemoryPool* pool) : _copier(pool) {
_copier.startString();
}
JsonString str() {
ARDUINOJSON_ASSERT(_size < _capacity);
return JsonString(_pool->saveStringFromFreeZone(_size), _size,
JsonString::Copied);
ARDUINOJSON_ASSERT(!overflowed());
return _copier.save();
}
size_t write(uint8_t c) {
if (_size >= _capacity)
return 0;
_string[_size++] = char(c);
return 1;
_copier.append(char(c));
return _copier.isValid() ? 1 : 0;
}
size_t write(const uint8_t* buffer, size_t size) {
if (_size + size >= _capacity) {
_size = _capacity; // mark as overflowed
return 0;
for (size_t i = 0; i < size; i++) {
_copier.append(char(buffer[i]));
if (!_copier.isValid())
return i;
}
memcpy(&_string[_size], buffer, size);
_size += size;
return size;
}
bool overflowed() const {
return _size >= _capacity;
return !_copier.isValid();
}
private:
MemoryPool* _pool;
size_t _size;
char* _string;
size_t _capacity;
StringCopier _copier;
};
} // namespace detail
@@ -253,7 +246,6 @@ inline void convertToJson(const ::Printable& src, JsonVariant dst) {
detail::MemoryPoolPrint print(pool);
src.printTo(print);
if (print.overflowed()) {
pool->markAsOverflowed();
data->setNull();
return;
}
+3 -7
View File
@@ -226,9 +226,7 @@ class VariantData {
switch (type()) {
case VALUE_IS_OWNED_STRING:
case VALUE_IS_OWNED_RAW:
// We always add a zero at the end: the deduplication function uses it
// to detect the beginning of the next string.
return _content.asString.size + 1;
return sizeofString(_content.asString.size);
case VALUE_IS_OBJECT:
case VALUE_IS_ARRAY:
return _content.asCollection.memoryUsage();
@@ -277,11 +275,9 @@ class VariantData {
return _content.asCollection.getOrAddMember(key, pool);
}
void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance) {
if (_flags & OWNED_VALUE_BIT)
_content.asString.data += stringDistance;
void movePointers(ptrdiff_t variantDistance) {
if (_flags & COLLECTION_MASK)
_content.asCollection.movePointers(stringDistance, variantDistance);
_content.asCollection.movePointers(variantDistance);
}
uint8_t type() const {
+2 -6
View File
@@ -99,13 +99,9 @@ class VariantSlot {
_key = 0;
}
void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance) {
if (_flags & OWNED_KEY_BIT)
_key += stringDistance;
if (_flags & OWNED_VALUE_BIT)
_content.asString.data += stringDistance;
void movePointers(ptrdiff_t variantDistance) {
if (_flags & COLLECTION_MASK)
_content.asCollection.movePointers(stringDistance, variantDistance);
_content.asCollection.movePointers(variantDistance);
}
};