Skip to content

Commit 1eddeb0

Browse files
committed
Optimize storage of tiny strings (up to 3 characters)
1 parent cb1dcfa commit 1eddeb0

File tree

7 files changed

+66
-6
lines changed

7 files changed

+66
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ HEAD
55
----
66

77
* Fix conversion from static string to number
8-
* Slightly reduce code size
8+
* Optimize storage of tiny strings (up to 3 characters)
99

1010
v7.3.0 (2024-12-29)
1111
------

extras/tests/JsonObject/set.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ TEST_CASE("JsonObject::set()") {
100100
JsonDocument doc3(&timebomb);
101101
JsonObject obj3 = doc3.to<JsonObject>();
102102

103-
obj1["a"_s] = 1;
104-
obj1["b"_s] = 2;
103+
obj1["alpha"_s] = 1;
104+
obj1["beta"_s] = 2;
105105

106106
bool success = obj3.set(obj1);
107107

108108
REQUIRE(success == false);
109-
REQUIRE(doc3.as<std::string>() == "{\"a\":1}");
109+
REQUIRE(doc3.as<std::string>() == "{\"alpha\":1}");
110110
}
111111

112112
SECTION("copy fails in the middle of an array") {

extras/tests/JsonVariant/as.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ TEST_CASE("JsonVariant::as()") {
199199
REQUIRE(variant.as<JsonString>() == "hello");
200200
}
201201

202-
SECTION("set(std::string(\"4.2\"))") {
202+
SECTION("set(std::string(\"4.2\")) (tiny string optimization)") {
203203
variant.set("4.2"_s);
204204

205205
REQUIRE(variant.as<bool>() == true);
@@ -211,6 +211,18 @@ TEST_CASE("JsonVariant::as()") {
211211
REQUIRE(variant.as<JsonString>().isStatic() == false);
212212
}
213213

214+
SECTION("set(std::string(\"123.45\"))") {
215+
variant.set("123.45"_s);
216+
217+
REQUIRE(variant.as<bool>() == true);
218+
REQUIRE(variant.as<long>() == 123L);
219+
REQUIRE(variant.as<double>() == Approx(123.45));
220+
REQUIRE(variant.as<const char*>() == "123.45"_s);
221+
REQUIRE(variant.as<std::string>() == "123.45"_s);
222+
REQUIRE(variant.as<JsonString>() == "123.45");
223+
REQUIRE(variant.as<JsonString>().isStatic() == false);
224+
}
225+
214226
SECTION("set(\"true\")") {
215227
variant.set("true");
216228

extras/tests/JsonVariant/set.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
6363
});
6464
}
6565

66+
SECTION("char* (tiny string optimization)") {
67+
char str[16];
68+
69+
strcpy(str, "abc");
70+
bool result = variant.set(str);
71+
strcpy(str, "def");
72+
73+
REQUIRE(result == true);
74+
REQUIRE(variant == "abc"); // stores by copy
75+
REQUIRE(spy.log() == AllocatorLog{});
76+
}
77+
6678
SECTION("(char*)0") {
6779
bool result = variant.set(static_cast<char*>(0));
6880

src/ArduinoJson/Variant/VariantContent.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ enum class VariantTypeBits : uint8_t {
2424

2525
enum class VariantType : uint8_t {
2626
Null = 0, // 0000 0000
27+
TinyString = 0x02, // 0000 0010
2728
RawString = 0x03, // 0000 0011
2829
LinkedString = 0x04, // 0000 0100
2930
OwnedString = 0x05, // 0000 0101
@@ -46,6 +47,8 @@ inline bool operator&(VariantType type, VariantTypeBits bit) {
4647
return (uint8_t(type) & uint8_t(bit)) != 0;
4748
}
4849

50+
const size_t tinyStringMaxLength = 3;
51+
4952
union VariantContent {
5053
VariantContent() {}
5154

@@ -61,6 +64,7 @@ union VariantContent {
6164
CollectionData asCollection;
6265
const char* asLinkedString;
6366
struct StringNode* asOwnedString;
67+
char asTinyString[tinyStringMaxLength + 1];
6468
};
6569

6670
#if ARDUINOJSON_USE_EXTENSIONS

src/ArduinoJson/Variant/VariantData.hpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class VariantData {
6363
case VariantType::Object:
6464
return visit.visit(content_.asObject);
6565

66+
case VariantType::TinyString:
67+
return visit.visit(JsonString(content_.asTinyString));
68+
6669
case VariantType::LinkedString:
6770
return visit.visit(JsonString(content_.asLinkedString, true));
6871

@@ -199,6 +202,9 @@ class VariantData {
199202
case VariantType::Int64:
200203
return static_cast<T>(extension->asInt64);
201204
#endif
205+
case VariantType::TinyString:
206+
str = content_.asTinyString;
207+
break;
202208
case VariantType::LinkedString:
203209
str = content_.asLinkedString;
204210
break;
@@ -241,6 +247,9 @@ class VariantData {
241247
case VariantType::Int64:
242248
return convertNumber<T>(extension->asInt64);
243249
#endif
250+
case VariantType::TinyString:
251+
str = content_.asTinyString;
252+
break;
244253
case VariantType::LinkedString:
245254
str = content_.asLinkedString;
246255
break;
@@ -281,6 +290,8 @@ class VariantData {
281290

282291
JsonString asString() const {
283292
switch (type_) {
293+
case VariantType::TinyString:
294+
return JsonString(content_.asTinyString);
284295
case VariantType::LinkedString:
285296
return JsonString(content_.asLinkedString, true);
286297
case VariantType::OwnedString:
@@ -395,7 +406,8 @@ class VariantData {
395406

396407
bool isString() const {
397408
return type_ == VariantType::LinkedString ||
398-
type_ == VariantType::OwnedString;
409+
type_ == VariantType::OwnedString ||
410+
type_ == VariantType::TinyString;
399411
}
400412

401413
size_t nesting(const ResourceManager* resources) const {
@@ -509,6 +521,14 @@ class VariantData {
509521
content_.asLinkedString = s;
510522
}
511523

524+
void setTinyString(const char* s, uint8_t n) {
525+
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
526+
ARDUINOJSON_ASSERT(s);
527+
type_ = VariantType::TinyString;
528+
for (uint8_t i = 0; i < n + 1; i++)
529+
content_.asTinyString[i] = s[i];
530+
}
531+
512532
void setOwnedString(StringNode* s) {
513533
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
514534
ARDUINOJSON_ASSERT(s);

src/ArduinoJson/Variant/VariantImpl.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ inline bool VariantData::setString(TAdaptedString value,
3131
return true;
3232
}
3333

34+
if (value.size() <= tinyStringMaxLength) {
35+
uint8_t length = static_cast<uint8_t>(value.size());
36+
bool containsNul = false;
37+
for (uint8_t i = 0; i < length; i++)
38+
containsNul |= !value[i];
39+
bool nulTerminated = value[length] == 0;
40+
if (!containsNul && nulTerminated) {
41+
setTinyString(value.data(), length);
42+
return true;
43+
}
44+
}
45+
3446
auto dup = resources->saveString(value);
3547
if (dup) {
3648
setOwnedString(dup);

0 commit comments

Comments
 (0)