From ed14ff9535b13d0612dc645d9b6692536a8d81e5 Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Fri, 3 Oct 2025 18:54:19 +0300 Subject: [PATCH 1/7] fix: compute_memory_size() added --- include/jsoncons/basic_json.hpp | 152 ++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/include/jsoncons/basic_json.hpp b/include/jsoncons/basic_json.hpp index 0896ab0475..f3a9524ea5 100644 --- a/include/jsoncons/basic_json.hpp +++ b/include/jsoncons/basic_json.hpp @@ -42,6 +42,9 @@ #if defined(JSONCONS_HAS_POLYMORPHIC_ALLOCATOR) #include // std::poymorphic_allocator #endif +#if defined(JSONCONS_COMPUTE_MEMORY_SIZE) +#include // mi_usable_size +#endif namespace jsoncons { @@ -1597,6 +1600,146 @@ namespace jsoncons { return ptr; } +#if defined(JSONCONS_COMPUTE_MEMORY_SIZE) + // Helper function to get usable size of allocated memory + // Uses mi_usable_size() + template + static std::size_t get_usable_size(const T* ptr) + { + if (ptr == nullptr) + { + return 0; + } + return mi_usable_size(const_cast(ptr)); + } + + // Recursive implementation of compute_memory_size + std::size_t compute_memory_size_impl() const + { + std::size_t mem_size = 0; + + switch (storage_kind()) + { + case json_storage_kind::null: + case json_storage_kind::empty_object: + case json_storage_kind::boolean: + case json_storage_kind::int64: + case json_storage_kind::uint64: + case json_storage_kind::half_float: + case json_storage_kind::float64: + // These are stored inline, no dynamic allocation + mem_size = 0; + break; + + case json_storage_kind::short_str: + // Short string optimization - stored inline + mem_size = 0; + break; + + case json_storage_kind::long_str: + { + // Get the string data pointer and compute its allocated size + const auto& storage = cast(); + const char_type* str_ptr = storage.data(); + + // Use mi_usable_size to get actual allocated size + mem_size = get_usable_size(str_ptr); + break; + } + + case json_storage_kind::byte_str: + { + // Similar to long_str + const auto& storage = cast(); + const uint8_t* data_ptr = storage.data(); + + mem_size = get_usable_size(data_ptr); + break; + } + + case json_storage_kind::array: + { + // Get array internal storage + const array& arr = cast().value(); + + // Memory for the array object itself + mem_size += sizeof(array); + + // Memory for the array's internal buffer + // Arrays use std::vector which guarantees contiguous storage + // We can get pointer via &arr[0] if size > 0 + if (!arr.empty()) + { + // Get pointer to internal vector buffer + const basic_json* data_ptr = &arr[0]; + // Use mi_usable_size() for precise allocated size + mem_size += get_usable_size(data_ptr); + } + + // Recursively compute size of each element + for (const auto& elem : arr) + { + mem_size += elem.compute_memory_size_impl(); + } + break; + } + + case json_storage_kind::object: + { + // Get object internal storage + const object& obj = cast().value(); + + // Memory for the object itself + mem_size += sizeof(object); + + // Memory for the object's internal storage (vector of key_value_type) + // Objects use std::vector internally (sorted_json_object or order_preserving_json_object) + if (!obj.empty()) + { + // Get pointer to internal vector buffer via iterator + const key_value_type* data_ptr = &(*obj.begin()); + // Use mi_usable_size() for precise allocated size + mem_size += get_usable_size(data_ptr); + } + + // Recursively compute size of keys and values + for (const auto& member : obj) + { + // Key size: key_type is std::basic_string + // The mi_usable_size() above already includes the inline part of keys. + // Here we only need to count dynamic memory for keys that exceed SSO buffer + const auto& key_str = member.key(); + + // Use mi_usable_size() to determine if key has heap allocation + // For SSO strings, mi_usable_size() returns 0 (not heap allocated) + // For heap-allocated strings, returns the actual allocated size + const char_type* key_data = key_str.data(); + std::size_t key_heap_size = get_usable_size(key_data); + mem_size += key_heap_size; + + // Value size (recursive for nested structures) + mem_size += member.value().compute_memory_size_impl(); + } + break; + } + + case json_storage_kind::const_json_pointer: + { + // This is just a pointer to another JSON value, no ownership + mem_size = 0; + break; + } + + default: + // Unknown storage type + mem_size = 0; + break; + } + + return mem_size; + } +#endif + template void construct(Args&&... args) { @@ -2111,6 +2254,15 @@ namespace jsoncons { } } +#if defined(JSONCONS_COMPUTE_MEMORY_SIZE) + // Computes the actual memory size used by this JSON value + // including all dynamically allocated memory. + std::size_t compute_memory_size() const + { + return compute_memory_size_impl(); + } +#endif + string_view_type as_string_view() const { switch (storage_kind()) From ffe34fb56530a58137a82d00bdee58804a2cb589 Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Wed, 8 Oct 2025 18:20:50 +0300 Subject: [PATCH 2/7] fix: compute_memory_size() updated --- include/jsoncons/basic_json.hpp | 60 ++++++++++++++------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/include/jsoncons/basic_json.hpp b/include/jsoncons/basic_json.hpp index f3a9524ea5..a7402e000a 100644 --- a/include/jsoncons/basic_json.hpp +++ b/include/jsoncons/basic_json.hpp @@ -21,6 +21,7 @@ #include // std::move #include // std::enable_if #include // std::basic_istream +#include // std::function #include #include #include @@ -42,9 +43,6 @@ #if defined(JSONCONS_HAS_POLYMORPHIC_ALLOCATOR) #include // std::poymorphic_allocator #endif -#if defined(JSONCONS_COMPUTE_MEMORY_SIZE) -#include // mi_usable_size -#endif namespace jsoncons { @@ -1600,21 +1598,11 @@ namespace jsoncons { return ptr; } -#if defined(JSONCONS_COMPUTE_MEMORY_SIZE) - // Helper function to get usable size of allocated memory - // Uses mi_usable_size() - template - static std::size_t get_usable_size(const T* ptr) - { - if (ptr == nullptr) - { - return 0; - } - return mi_usable_size(const_cast(ptr)); - } + // Callback type for getting usable size of allocated memory + using memory_size_callback = std::function; - // Recursive implementation of compute_memory_size - std::size_t compute_memory_size_impl() const + // Recursive implementation of compute_memory_size with callback + std::size_t compute_memory_size_impl(const memory_size_callback& get_usable_size) const { std::size_t mem_size = 0; @@ -1642,8 +1630,8 @@ namespace jsoncons { const auto& storage = cast(); const char_type* str_ptr = storage.data(); - // Use mi_usable_size to get actual allocated size - mem_size = get_usable_size(str_ptr); + // Use callback to get actual allocated size + mem_size = get_usable_size(static_cast(str_ptr)); break; } @@ -1653,7 +1641,8 @@ namespace jsoncons { const auto& storage = cast(); const uint8_t* data_ptr = storage.data(); - mem_size = get_usable_size(data_ptr); + // Use callback to get actual allocated size + mem_size = get_usable_size(static_cast(data_ptr)); break; } @@ -1672,14 +1661,14 @@ namespace jsoncons { { // Get pointer to internal vector buffer const basic_json* data_ptr = &arr[0]; - // Use mi_usable_size() for precise allocated size - mem_size += get_usable_size(data_ptr); + // Use callback for precise allocated size + mem_size += get_usable_size(static_cast(data_ptr)); } // Recursively compute size of each element for (const auto& elem : arr) { - mem_size += elem.compute_memory_size_impl(); + mem_size += elem.compute_memory_size_impl(get_usable_size); } break; } @@ -1698,27 +1687,27 @@ namespace jsoncons { { // Get pointer to internal vector buffer via iterator const key_value_type* data_ptr = &(*obj.begin()); - // Use mi_usable_size() for precise allocated size - mem_size += get_usable_size(data_ptr); + // Use callback for precise allocated size + mem_size += get_usable_size(static_cast(data_ptr)); } // Recursively compute size of keys and values for (const auto& member : obj) { // Key size: key_type is std::basic_string - // The mi_usable_size() above already includes the inline part of keys. + // The callback above already includes the inline part of keys. // Here we only need to count dynamic memory for keys that exceed SSO buffer const auto& key_str = member.key(); - // Use mi_usable_size() to determine if key has heap allocation - // For SSO strings, mi_usable_size() returns 0 (not heap allocated) + // Use callback to determine if key has heap allocation + // For SSO strings, callback returns 0 (not heap allocated) // For heap-allocated strings, returns the actual allocated size const char_type* key_data = key_str.data(); - std::size_t key_heap_size = get_usable_size(key_data); + std::size_t key_heap_size = get_usable_size(static_cast(key_data)); mem_size += key_heap_size; // Value size (recursive for nested structures) - mem_size += member.value().compute_memory_size_impl(); + mem_size += member.value().compute_memory_size_impl(get_usable_size); } break; } @@ -1738,7 +1727,6 @@ namespace jsoncons { return mem_size; } -#endif template void construct(Args&&... args) @@ -2254,14 +2242,16 @@ namespace jsoncons { } } -#if defined(JSONCONS_COMPUTE_MEMORY_SIZE) // Computes the actual memory size used by this JSON value // including all dynamically allocated memory. - std::size_t compute_memory_size() const + // + // Example usage with mimalloc: + // auto cb = [](const void* ptr) { return ptr ? mi_usable_size(const_cast(ptr)) : 0; }; + // size_t size = json_obj.compute_memory_size(cb); + std::size_t compute_memory_size(const memory_size_callback& get_usable_size) const { - return compute_memory_size_impl(); + return compute_memory_size_impl(get_usable_size); } -#endif string_view_type as_string_view() const { From f91d875b50c7581d84d796d27bb6dd8c64932416 Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Fri, 10 Oct 2025 13:34:38 +0300 Subject: [PATCH 3/7] fix: review comments --- include/jsoncons/basic_json.hpp | 250 +++++++++++++++----------------- 1 file changed, 119 insertions(+), 131 deletions(-) diff --git a/include/jsoncons/basic_json.hpp b/include/jsoncons/basic_json.hpp index a7402e000a..77f020c220 100644 --- a/include/jsoncons/basic_json.hpp +++ b/include/jsoncons/basic_json.hpp @@ -1598,136 +1598,6 @@ namespace jsoncons { return ptr; } - // Callback type for getting usable size of allocated memory - using memory_size_callback = std::function; - - // Recursive implementation of compute_memory_size with callback - std::size_t compute_memory_size_impl(const memory_size_callback& get_usable_size) const - { - std::size_t mem_size = 0; - - switch (storage_kind()) - { - case json_storage_kind::null: - case json_storage_kind::empty_object: - case json_storage_kind::boolean: - case json_storage_kind::int64: - case json_storage_kind::uint64: - case json_storage_kind::half_float: - case json_storage_kind::float64: - // These are stored inline, no dynamic allocation - mem_size = 0; - break; - - case json_storage_kind::short_str: - // Short string optimization - stored inline - mem_size = 0; - break; - - case json_storage_kind::long_str: - { - // Get the string data pointer and compute its allocated size - const auto& storage = cast(); - const char_type* str_ptr = storage.data(); - - // Use callback to get actual allocated size - mem_size = get_usable_size(static_cast(str_ptr)); - break; - } - - case json_storage_kind::byte_str: - { - // Similar to long_str - const auto& storage = cast(); - const uint8_t* data_ptr = storage.data(); - - // Use callback to get actual allocated size - mem_size = get_usable_size(static_cast(data_ptr)); - break; - } - - case json_storage_kind::array: - { - // Get array internal storage - const array& arr = cast().value(); - - // Memory for the array object itself - mem_size += sizeof(array); - - // Memory for the array's internal buffer - // Arrays use std::vector which guarantees contiguous storage - // We can get pointer via &arr[0] if size > 0 - if (!arr.empty()) - { - // Get pointer to internal vector buffer - const basic_json* data_ptr = &arr[0]; - // Use callback for precise allocated size - mem_size += get_usable_size(static_cast(data_ptr)); - } - - // Recursively compute size of each element - for (const auto& elem : arr) - { - mem_size += elem.compute_memory_size_impl(get_usable_size); - } - break; - } - - case json_storage_kind::object: - { - // Get object internal storage - const object& obj = cast().value(); - - // Memory for the object itself - mem_size += sizeof(object); - - // Memory for the object's internal storage (vector of key_value_type) - // Objects use std::vector internally (sorted_json_object or order_preserving_json_object) - if (!obj.empty()) - { - // Get pointer to internal vector buffer via iterator - const key_value_type* data_ptr = &(*obj.begin()); - // Use callback for precise allocated size - mem_size += get_usable_size(static_cast(data_ptr)); - } - - // Recursively compute size of keys and values - for (const auto& member : obj) - { - // Key size: key_type is std::basic_string - // The callback above already includes the inline part of keys. - // Here we only need to count dynamic memory for keys that exceed SSO buffer - const auto& key_str = member.key(); - - // Use callback to determine if key has heap allocation - // For SSO strings, callback returns 0 (not heap allocated) - // For heap-allocated strings, returns the actual allocated size - const char_type* key_data = key_str.data(); - std::size_t key_heap_size = get_usable_size(static_cast(key_data)); - mem_size += key_heap_size; - - // Value size (recursive for nested structures) - mem_size += member.value().compute_memory_size_impl(get_usable_size); - } - break; - } - - case json_storage_kind::const_json_pointer: - { - // This is just a pointer to another JSON value, no ownership - mem_size = 0; - break; - } - - default: - // Unknown storage type - mem_size = 0; - break; - } - - return mem_size; - } - template void construct(Args&&... args) { @@ -2242,15 +2112,133 @@ namespace jsoncons { } } + // Callback type for getting usable size of allocated memory + using memory_size_callback = std::function; + // Computes the actual memory size used by this JSON value // including all dynamically allocated memory. + // Uses iterative traversal (not recursion) to avoid stack overflow. // // Example usage with mimalloc: // auto cb = [](const void* ptr) { return ptr ? mi_usable_size(const_cast(ptr)) : 0; }; // size_t size = json_obj.compute_memory_size(cb); std::size_t compute_memory_size(const memory_size_callback& get_usable_size) const { - return compute_memory_size_impl(get_usable_size); + std::size_t mem_size = 0; + + // Use explicit stack for iterative traversal (avoids recursion/stack overflow) + std::vector stack; + stack.reserve(64); // Reserve some space to reduce allocations + stack.push_back(this); + + while (!stack.empty()) + { + const basic_json* current = stack.back(); + stack.pop_back(); + + switch (current->storage_kind()) + { + case json_storage_kind::null: + case json_storage_kind::empty_object: + case json_storage_kind::boolean: + case json_storage_kind::int64: + case json_storage_kind::uint64: + case json_storage_kind::half_float: + case json_storage_kind::float64: + case json_storage_kind::short_str: + // These are stored inline, no dynamic allocation + break; + + case json_storage_kind::long_str: + { + // Get the string data pointer and compute its allocated size + const auto& storage = current->template cast(); + const char_type* str_ptr = storage.data(); + + // Use callback to get actual allocated size + mem_size += get_usable_size(static_cast(str_ptr)); + break; + } + + case json_storage_kind::byte_str: + { + // Similar to long_str + const auto& storage = current->template cast(); + const uint8_t* data_ptr = storage.data(); + + // Use callback to get actual allocated size + mem_size += get_usable_size(static_cast(data_ptr)); + break; + } + + case json_storage_kind::array: + { + // Get array internal storage + const array& arr = current->template cast().value(); + + // Memory for the array object itself + mem_size += sizeof(array); + + // Memory for the array's internal buffer + if (!arr.empty()) + { + // Get pointer to internal vector buffer + const basic_json* data_ptr = &arr[0]; + // Use callback for precise allocated size + mem_size += get_usable_size(static_cast(data_ptr)); + + // Add all array elements to stack for processing (iterative, not recursive) + for (const auto& elem : arr) + { + stack.push_back(&elem); + } + } + break; + } + + case json_storage_kind::object: + { + // Get object internal storage + const object& obj = current->template cast().value(); + + // Memory for the object itself + mem_size += sizeof(object); + + // Memory for the object's internal storage (vector of key_value_type) + if (!obj.empty()) + { + // Get pointer to internal vector buffer via iterator + const key_value_type* data_ptr = &(*obj.begin()); + // Use callback for precise allocated size + mem_size += get_usable_size(static_cast(data_ptr)); + } + + // Process keys and values + for (const auto& member : obj) + { + // Key size: check if key has heap allocation + const auto& key_str = member.key(); + const char_type* key_data = key_str.data(); + std::size_t key_heap_size = get_usable_size(static_cast(key_data)); + mem_size += key_heap_size; + + // Add value to stack for processing (iterative, not recursive) + stack.push_back(&member.value()); + } + break; + } + + case json_storage_kind::const_json_pointer: + // This is just a pointer to another JSON value, no ownership + break; + + default: + // Unknown storage type + break; + } + } + + return mem_size; } string_view_type as_string_view() const From 449ef7546e1ed49033e3189e7297d85e6270ce45 Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Sun, 12 Oct 2025 16:18:26 +0300 Subject: [PATCH 4/7] Update include/jsoncons/basic_json.hpp Co-authored-by: Roman Gershman Signed-off-by: Volodymyr Yavdoshenko --- include/jsoncons/basic_json.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/jsoncons/basic_json.hpp b/include/jsoncons/basic_json.hpp index 77f020c220..c367de7110 100644 --- a/include/jsoncons/basic_json.hpp +++ b/include/jsoncons/basic_json.hpp @@ -2128,7 +2128,7 @@ namespace jsoncons { // Use explicit stack for iterative traversal (avoids recursion/stack overflow) std::vector stack; - stack.reserve(64); // Reserve some space to reduce allocations + stack.reserve(8); // Reserve some space to reduce allocations stack.push_back(this); while (!stack.empty()) From c2694e055eb408976403b95d2409c7af7ae7f5af Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Sun, 12 Oct 2025 16:34:03 +0300 Subject: [PATCH 5/7] fix: compute_memory_size() updated --- include/jsoncons/basic_json.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/include/jsoncons/basic_json.hpp b/include/jsoncons/basic_json.hpp index c367de7110..d3ff07d5d0 100644 --- a/include/jsoncons/basic_json.hpp +++ b/include/jsoncons/basic_json.hpp @@ -2176,10 +2176,9 @@ namespace jsoncons { // Get array internal storage const array& arr = current->template cast().value(); - // Memory for the array object itself - mem_size += sizeof(array); - // Memory for the array's internal buffer + // Note: We only count dynamically allocated memory (heap). + // The array object itself is part of array_storage allocation. if (!arr.empty()) { // Get pointer to internal vector buffer @@ -2201,10 +2200,9 @@ namespace jsoncons { // Get object internal storage const object& obj = current->template cast().value(); - // Memory for the object itself - mem_size += sizeof(object); - // Memory for the object's internal storage (vector of key_value_type) + // Note: We only count dynamically allocated memory (heap). + // The object itself is part of object_storage allocation. if (!obj.empty()) { // Get pointer to internal vector buffer via iterator From 5f09b06f83467fbe8431004763f8e8f0a9598dbb Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Sun, 12 Oct 2025 16:42:04 +0300 Subject: [PATCH 6/7] fix: compute_memory_size() updated --- include/jsoncons/basic_json.hpp | 35 +++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/include/jsoncons/basic_json.hpp b/include/jsoncons/basic_json.hpp index d3ff07d5d0..dd3b23b99d 100644 --- a/include/jsoncons/basic_json.hpp +++ b/include/jsoncons/basic_json.hpp @@ -2186,10 +2186,23 @@ namespace jsoncons { // Use callback for precise allocated size mem_size += get_usable_size(static_cast(data_ptr)); - // Add all array elements to stack for processing (iterative, not recursive) + // Add array elements to stack for processing + // Optimization: only add elements that have dynamic memory for (const auto& elem : arr) { - stack.push_back(&elem); + auto kind = elem.storage_kind(); + // Skip inline storage types (primitives, short strings, empty objects) + if (kind != json_storage_kind::null && + kind != json_storage_kind::boolean && + kind != json_storage_kind::int64 && + kind != json_storage_kind::uint64 && + kind != json_storage_kind::half_float && + kind != json_storage_kind::float64 && + kind != json_storage_kind::short_str && + kind != json_storage_kind::empty_object) + { + stack.push_back(&elem); + } } } break; @@ -2220,8 +2233,22 @@ namespace jsoncons { std::size_t key_heap_size = get_usable_size(static_cast(key_data)); mem_size += key_heap_size; - // Add value to stack for processing (iterative, not recursive) - stack.push_back(&member.value()); + // Add value to stack for processing + // Optimization: only add values that have dynamic memory + const auto& value = member.value(); + auto kind = value.storage_kind(); + // Skip inline storage types (primitives, short strings, empty objects) + if (kind != json_storage_kind::null && + kind != json_storage_kind::boolean && + kind != json_storage_kind::int64 && + kind != json_storage_kind::uint64 && + kind != json_storage_kind::half_float && + kind != json_storage_kind::float64 && + kind != json_storage_kind::short_str && + kind != json_storage_kind::empty_object) + { + stack.push_back(&value); + } } break; } From e59fe1d67d61cf71596bda82b62c36a7928b5756 Mon Sep 17 00:00:00 2001 From: Volodymyr Yavdoshenko Date: Sun, 12 Oct 2025 23:31:36 +0300 Subject: [PATCH 7/7] fix: compute_memory_size() updated --- include/jsoncons/basic_json.hpp | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/include/jsoncons/basic_json.hpp b/include/jsoncons/basic_json.hpp index dd3b23b99d..2e9684815c 100644 --- a/include/jsoncons/basic_json.hpp +++ b/include/jsoncons/basic_json.hpp @@ -2187,19 +2187,11 @@ namespace jsoncons { mem_size += get_usable_size(static_cast(data_ptr)); // Add array elements to stack for processing - // Optimization: only add elements that have dynamic memory + // Optimization: only add elements that need traversal (arrays/objects) for (const auto& elem : arr) { - auto kind = elem.storage_kind(); - // Skip inline storage types (primitives, short strings, empty objects) - if (kind != json_storage_kind::null && - kind != json_storage_kind::boolean && - kind != json_storage_kind::int64 && - kind != json_storage_kind::uint64 && - kind != json_storage_kind::half_float && - kind != json_storage_kind::float64 && - kind != json_storage_kind::short_str && - kind != json_storage_kind::empty_object) + // capacity() > 0 only for arrays/objects + if (elem.capacity() > 0) { stack.push_back(&elem); } @@ -2234,18 +2226,10 @@ namespace jsoncons { mem_size += key_heap_size; // Add value to stack for processing - // Optimization: only add values that have dynamic memory + // Optimization: only add values that need traversal (arrays/objects) const auto& value = member.value(); - auto kind = value.storage_kind(); - // Skip inline storage types (primitives, short strings, empty objects) - if (kind != json_storage_kind::null && - kind != json_storage_kind::boolean && - kind != json_storage_kind::int64 && - kind != json_storage_kind::uint64 && - kind != json_storage_kind::half_float && - kind != json_storage_kind::float64 && - kind != json_storage_kind::short_str && - kind != json_storage_kind::empty_object) + // capacity() > 0 only for arrays/objects + if (value.capacity() > 0) { stack.push_back(&value); }