From 49f81d8fae9d5eea1ce2b44ed786ec6ed25174b7 Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Fri, 5 Jul 2024 14:19:14 +0200 Subject: [PATCH] feat: improve json object memory consumption --- src/rbjson.cpp | 72 ++++++++++++++++++++++++++++++++++++-------------- src/rbjson.h | 20 +++++++++++++- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/rbjson.cpp b/src/rbjson.cpp index b44de6f..9070351 100644 --- a/src/rbjson.cpp +++ b/src/rbjson.cpp @@ -200,16 +200,17 @@ Object::Object() Object::~Object() { for (auto itr = m_members.begin(); itr != m_members.end(); ++itr) { - delete itr->second; + delete itr->value; + free(itr->name); } } void Object::serialize(std::ostream& ss) const { ss.put('{'); for (auto itr = m_members.cbegin(); itr != m_members.cend();) { - write_string_escaped(itr->first.c_str(), ss); + write_string_escaped(itr->name, ss); ss.put(':'); - itr->second->serialize(ss); + itr->value->serialize(ss); if (++itr != m_members.cend()) { ss.put(','); } @@ -221,6 +222,25 @@ void Object::swapData(Object& other) { m_members.swap(other.m_members); } +bool Object::keyLess(const MemberItem& member, const std::string& key) { + return strncmp(member.name, key.c_str(), std::min(size_t(254), key.length())) < 0; +} + +bool Object::keyEqualStr(const MemberItem& a, const std::string& key) { + const uint8_t key_len = std::min(size_t(254), key.length()); + if(a.name_len != key_len) { + return false; + } + return memcmp(a.name, key.c_str(), a.name_len) == 0; +} + +bool Object::keyEqual(const MemberItem& a, const MemberItem& b) { + if(a.name_len != b.name_len) { + return false; + } + return memcmp(a.name, b.name, a.name_len) == 0; +} + bool Object::equals(const Value& other) const { if (!Value::equals(other)) return false; @@ -234,7 +254,7 @@ bool Object::equals(const Value& other) const { auto itr_a = m_members.cbegin(); auto itr_b = obj.m_members.cbegin(); for(size_t i = 0; i < size; ++i) { - if(itr_a->first != itr_b->first || !itr_a->second->equals(*itr_b->second)) + if(!keyEqual(*itr_a, *itr_b) || !itr_a->value->equals(*itr_b->value)) return false; ++itr_a; ++itr_b; @@ -245,34 +265,36 @@ bool Object::equals(const Value& other) const { Value* Object::copy() const { auto* res = new Object(); + res->m_members.reserve(m_members.size()); for (const auto& pair : m_members) { - res->m_members.emplace_back(std::make_pair(pair.first, pair.second->copy())); + res->m_members.emplace_back(MemberItem{ + .value = pair.value->copy(), + .name = strdup(pair.name), + .name_len = pair.name_len, + }); } + res->m_members.shrink_to_fit(); return res; } Object::container_t::const_iterator Object::lower_bound_const(const std::string& key) const { - return std::lower_bound(m_members.cbegin(), m_members.cend(), key, [] (const auto& itr, const std::string& key) { - return itr.first < key; - }); + return std::lower_bound(m_members.cbegin(), m_members.cend(), key, keyLess); } Object::container_t::iterator Object::lower_bound(const std::string& key) { - return std::lower_bound(m_members.begin(), m_members.end(), key, [] (const auto& itr, const std::string& key) { - return itr.first < key; - }); + return std::lower_bound(m_members.begin(), m_members.end(), key, keyLess); } bool Object::contains(const std::string& key) const { const auto lower = lower_bound_const(key); - return lower != m_members.end() && lower->first == key; + return lower != m_members.end() && keyEqualStr(*lower, key); } Value* Object::get(const std::string& key) const { const auto lower = lower_bound_const(key); - if (lower == m_members.end() || lower->first != key) + if (lower == m_members.end() || !keyEqualStr(*lower, key)) return NULL; - return lower->second; + return lower->value; } Object* Object::getObject(const std::string& key) const { @@ -329,11 +351,20 @@ bool Object::getBool(const std::string& key, bool def) const { void Object::set(const std::string& key, Value* value) { auto lower = lower_bound(key); - if (lower != m_members.end() && lower->first == key) { - delete lower->second; - lower->second = value; + if (lower != m_members.end() && keyEqualStr(*lower, key)) { + delete lower->value; + lower->value = value; } else { - m_members.emplace(lower, std::make_pair(key, value)); + uint8_t name_len = std::min(size_t(254), key.length()); + auto *name = (char*)malloc(name_len + 1); + memcpy(name, key.c_str(), name_len); + name[name_len] = 0; + + m_members.emplace(lower, MemberItem{ + .value = value, + .name = name, + .name_len = name_len, + }); } } @@ -347,8 +378,9 @@ void Object::set(const std::string& key, double number) { void Object::remove(const std::string& key) { const auto lower = lower_bound_const(key); - if (lower != m_members.end() && lower->first == key) { - delete lower->second; + if (lower != m_members.end() && keyEqualStr(*lower, key)) { + delete lower->value; + free(lower->name); m_members.erase(lower); } } diff --git a/src/rbjson.h b/src/rbjson.h index acedce6..cf58a2d 100644 --- a/src/rbjson.h +++ b/src/rbjson.h @@ -64,7 +64,13 @@ class Array; */ class Object : public Value { public: - typedef std::vector> container_t; + struct __attribute__ ((packed)) MemberItem { + Value *value; + char *name; + uint8_t name_len; + }; + + typedef std::vector container_t; static Object* parse(char* buf, size_t size); @@ -94,7 +100,15 @@ class Object : public Value { void remove(const std::string& key); + void shrink_to_fit() { + m_members.shrink_to_fit(); + } + private: + static bool keyLess(const MemberItem& member, const std::string& key); + static bool keyEqualStr(const MemberItem& a, const std::string& key); + static bool keyEqual(const MemberItem& a, const MemberItem& b); + container_t::const_iterator lower_bound_const(const std::string& key) const; container_t::iterator lower_bound(const std::string& key); @@ -130,6 +144,10 @@ class Array : public Value { } void remove(size_t idx); + void shrink_to_fit() { + m_items.shrink_to_fit(); + } + private: std::vector m_items; };