Skip to content

Commit 0030874

Browse files
committed
Reference-count shared strings
1 parent b7c8e0d commit 0030874

File tree

9 files changed

+127
-20
lines changed

9 files changed

+127
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ HEAD
1212
* Remove `ARDUINOJSON_ENABLE_STRING_DEDUPLICATION` (string deduplication cannot be enabled anymore)
1313
* Remove `JsonDocument::capacity()`
1414
* Store the strings in the heap
15+
* Reference-count shared strings

extras/tests/JsonDocument/garbageCollect.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ TEST_CASE("JsonDocument::garbageCollect()") {
3232
AllocatorLog() << AllocatorLog::Allocate(4096)
3333
<< AllocatorLog::Allocate(sizeofString(7))
3434
<< AllocatorLog::Deallocate(sizeofString(7))
35-
<< AllocatorLog::Deallocate(sizeofString(7))
3635
<< AllocatorLog::Deallocate(4096));
3736
}
3837

@@ -46,7 +45,7 @@ TEST_CASE("JsonDocument::garbageCollect()") {
4645
bool result = doc.garbageCollect();
4746

4847
REQUIRE(result == false);
49-
REQUIRE(doc.memoryUsage() == sizeofObject(2) + 2 * sizeofString(7));
48+
REQUIRE(doc.memoryUsage() == sizeofObject(2) + sizeofString(7));
5049
REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
5150

5251
REQUIRE(spyingAllocator.log() == AllocatorLog()

extras/tests/JsonVariant/clear.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
#include <stdint.h>
77
#include <catch.hpp>
88

9+
using ArduinoJson::detail::sizeofArray;
10+
using ArduinoJson::detail::sizeofString;
11+
912
TEST_CASE("JsonVariant::clear()") {
1013
JsonDocument doc(4096);
1114
JsonVariant var = doc.to<JsonVariant>();
@@ -23,4 +26,12 @@ TEST_CASE("JsonVariant::clear()") {
2326

2427
REQUIRE(var.isNull() == true);
2528
}
29+
30+
SECTION("releases owned string") {
31+
var.set(std::string("hello"));
32+
REQUIRE(doc.memoryUsage() == sizeofString(5));
33+
34+
var.clear();
35+
REQUIRE(doc.memoryUsage() == 0);
36+
}
2637
}

extras/tests/JsonVariant/remove.cpp

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,67 @@
66
#include <stdint.h>
77
#include <catch.hpp>
88

9-
TEST_CASE("JsonVariant::remove()") {
9+
using ArduinoJson::detail::sizeofArray;
10+
using ArduinoJson::detail::sizeofString;
11+
12+
TEST_CASE("JsonVariant::remove(int)") {
1013
JsonDocument doc(4096);
11-
JsonVariant var = doc.to<JsonVariant>();
1214

13-
SECTION("remove(int)") {
14-
var.add(1);
15-
var.add(2);
16-
var.add(3);
15+
SECTION("release top level strings") {
16+
doc.add(std::string("hello"));
17+
doc.add(std::string("hello"));
18+
doc.add(std::string("world"));
19+
20+
JsonVariant var = doc.as<JsonVariant>();
21+
REQUIRE(var.as<std::string>() == "[\"hello\",\"hello\",\"world\"]");
22+
REQUIRE(doc.memoryUsage() == sizeofArray(3) + 2 * sizeofString(5));
23+
24+
var.remove(1);
25+
REQUIRE(var.as<std::string>() == "[\"hello\",\"world\"]");
26+
REQUIRE(doc.memoryUsage() == sizeofArray(3) + 2 * sizeofString(5));
1727

1828
var.remove(1);
29+
REQUIRE(var.as<std::string>() == "[\"hello\"]");
30+
REQUIRE(doc.memoryUsage() == sizeofArray(3) + 1 * sizeofString(5));
1931

20-
REQUIRE(var.as<std::string>() == "[1,3]");
32+
var.remove(0);
33+
REQUIRE(var.as<std::string>() == "[]");
34+
REQUIRE(doc.memoryUsage() == sizeofArray(3));
2135
}
2236

23-
SECTION("remove(const char *)") {
24-
var["a"] = 1;
25-
var["b"] = 2;
37+
SECTION("release strings in nested array") {
38+
doc[0][0] = std::string("hello");
2639

27-
var.remove("a");
40+
JsonVariant var = doc.as<JsonVariant>();
41+
REQUIRE(var.as<std::string>() == "[[\"hello\"]]");
42+
REQUIRE(doc.memoryUsage() == 2 * sizeofArray(1) + sizeofString(5));
2843

29-
REQUIRE(var.as<std::string>() == "{\"b\":2}");
44+
var.remove(0);
45+
REQUIRE(var.as<std::string>() == "[]");
46+
REQUIRE(doc.memoryUsage() == 2 * sizeofArray(1));
3047
}
48+
}
3149

32-
SECTION("remove(std::string)") {
33-
var["a"] = 1;
34-
var["b"] = 2;
50+
TEST_CASE("JsonVariant::remove(const char *)") {
51+
JsonDocument doc(4096);
52+
JsonVariant var = doc.to<JsonVariant>();
3553

36-
var.remove(std::string("b"));
54+
var["a"] = 1;
55+
var["b"] = 2;
3756

38-
REQUIRE(var.as<std::string>() == "{\"a\":1}");
39-
}
57+
var.remove("a");
58+
59+
REQUIRE(var.as<std::string>() == "{\"b\":2}");
60+
}
61+
62+
TEST_CASE("JsonVariant::remove(std::string)") {
63+
JsonDocument doc(4096);
64+
JsonVariant var = doc.to<JsonVariant>();
65+
66+
var["a"] = 1;
67+
var["b"] = 2;
68+
69+
var.remove(std::string("b"));
70+
71+
REQUIRE(var.as<std::string>() == "{\"a\":1}");
4072
}

extras/tests/JsonVariant/set.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
#include "Allocators.hpp"
99

10+
using ArduinoJson::detail::sizeofObject;
11+
using ArduinoJson::detail::sizeofString;
12+
1013
enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 };
1114

1215
TEST_CASE("JsonVariant::set() when there is enough memory") {
@@ -172,3 +175,36 @@ TEST_CASE("JsonVariant::set(JsonDocument)") {
172175
serializeJson(doc2, json);
173176
REQUIRE(json == "{\"hello\":\"world\"}");
174177
}
178+
179+
TEST_CASE("JsonVariant::set() releases the previous value") {
180+
JsonDocument doc(1024);
181+
doc["hello"] = std::string("world");
182+
REQUIRE(doc.memoryUsage() == sizeofObject(1) + sizeofString(5));
183+
184+
JsonVariant v = doc["hello"];
185+
186+
SECTION("int") {
187+
v.set(42);
188+
REQUIRE(doc.memoryUsage() == sizeofObject(1));
189+
}
190+
191+
SECTION("bool") {
192+
v.set(false);
193+
REQUIRE(doc.memoryUsage() == sizeofObject(1));
194+
}
195+
196+
SECTION("const char*") {
197+
v.set("hello");
198+
REQUIRE(doc.memoryUsage() == sizeofObject(1));
199+
}
200+
201+
SECTION("float") {
202+
v.set(1.2);
203+
REQUIRE(doc.memoryUsage() == sizeofObject(1));
204+
}
205+
206+
SECTION("Serialized<const char*>") {
207+
v.set(serialized("[]"));
208+
REQUIRE(doc.memoryUsage() == sizeofObject(1));
209+
}
210+
}

src/ArduinoJson/Memory/MemoryPool.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ constexpr size_t sizeofObject(size_t n) {
2828
struct StringNode {
2929
struct StringNode* next;
3030
uint16_t length;
31+
uint16_t references;
3132
char data[1];
3233
};
3334

@@ -112,6 +113,7 @@ class MemoryPool {
112113

113114
auto node = findString(str);
114115
if (node) {
116+
node->references++;
115117
return node->data;
116118
}
117119

@@ -147,6 +149,7 @@ class MemoryPool {
147149
_allocator->allocate(sizeofString(length)));
148150
if (node) {
149151
node->length = uint16_t(length);
152+
node->references = 1;
150153
} else {
151154
_overflowed = true;
152155
}
@@ -170,6 +173,23 @@ class MemoryPool {
170173
_allocator->deallocate(node);
171174
}
172175

176+
void dereferenceString(const char* s) {
177+
StringNode* prev = nullptr;
178+
for (auto node = _strings; node; node = node->next) {
179+
if (node->data == s) {
180+
if (--node->references == 0) {
181+
if (prev)
182+
prev->next = node->next;
183+
else
184+
_strings = node->next;
185+
_allocator->deallocate(node);
186+
}
187+
return;
188+
}
189+
prev = node;
190+
}
191+
}
192+
173193
void clear() {
174194
_right = _end;
175195
_overflowed = false;

src/ArduinoJson/StringStorage/StringCopier.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class StringCopier {
3333
node = _pool->reallocString(_node, _size);
3434
_pool->addStringToList(node);
3535
_node = nullptr; // next time we need a new string
36+
} else {
37+
node->references++;
3638
}
3739
return JsonString(node->data, node->length, JsonString::Copied);
3840
}

src/ArduinoJson/Variant/SlotFunctions.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ inline VariantData* slotData(VariantSlot* slot) {
2424

2525
inline void slotRelease(const VariantSlot* slot, MemoryPool* pool) {
2626
ARDUINOJSON_ASSERT(slot != nullptr);
27+
if (slot->ownsKey())
28+
pool->dereferenceString(slot->key());
2729
variantRelease(slot->data(), pool);
2830
}
2931

src/ArduinoJson/Variant/VariantFunctions.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ inline typename TVisitor::result_type variantAccept(const VariantData* var,
3333

3434
inline void variantRelease(const VariantData* var, MemoryPool* pool) {
3535
ARDUINOJSON_ASSERT(var != nullptr);
36+
auto s = var->getOwnedString();
37+
if (s)
38+
pool->dereferenceString(s);
39+
3640
auto c = var->asCollection();
3741
if (c) {
3842
for (auto slot = c->head(); slot; slot = slot->next())

0 commit comments

Comments
 (0)