diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 4b0ff237d3..a50b860ad9 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -28,11 +28,19 @@ //=== Regressions +== {es} version 8.14.1 + +=== Enhancements + +* Improve memory allocation management for JSON processing to reduce memory usage. + (See {ml-pull}2676[#2676].) + == {es} version 8.14.0 === Bug Fixes * Remove ineffective optimizations for duplicate strings. (See {ml-pull}2652[#2652], issue: {ml-issue}2130[#2130].) +* Use custom Boost.JSON resource allocator. (See {ml-pull}2674[#2674].) == {es} version 8.13.0 diff --git a/include/api/CJsonOutputWriter.h b/include/api/CJsonOutputWriter.h index d5bae88698..e89e259c7c 100644 --- a/include/api/CJsonOutputWriter.h +++ b/include/api/CJsonOutputWriter.h @@ -273,6 +273,9 @@ class API_EXPORT CJsonOutputWriter { //! \p allocatorName A unique identifier for the allocator void pushAllocator(const std::string& allocatorName); + //! release the allocator + void releaseAllocator(const std::string& allocatorName); + //! revert to using the previous allocator for JSON output processing void popAllocator(); diff --git a/include/core/CBoostJsonPoolAllocator.h b/include/core/CBoostJsonPoolAllocator.h index 53788e65e3..ff16d724ae 100644 --- a/include/core/CBoostJsonPoolAllocator.h +++ b/include/core/CBoostJsonPoolAllocator.h @@ -20,14 +20,34 @@ namespace json = boost::json; namespace ml { namespace core { + +namespace { + +class custom_resource : public boost::container::pmr::memory_resource { +private: + void* do_allocate(std::size_t bytes, std::size_t /*align*/) override { + return ::operator new(bytes); + } + + void do_deallocate(void* ptr, std::size_t /*bytes*/, std::size_t /*align*/) override { + return ::operator delete(ptr); + } + + bool do_is_equal(memory_resource const& other) const noexcept override { + // since the global allocation and de-allocation functions are used, + // any instance of a custom_resource can deallocate memory allocated + // by another instance of a logging_resource + return dynamic_cast(&other) != nullptr; + } +}; +} //! \brief -//! A boost::json memory allocator using a fixed size buffer +//! A custom boost::json memory allocator //! //! DESCRIPTION:\n -//! Encapsulates a boost::json monotonic_resource optimized with a fixed size buffer, see https://www.boost.org/doc/libs/1_83_0/libs/json/doc/html/json/allocators/storage_ptr.html +//! Encapsulates a custom boost::json memory_resource, see https://www.boost.org/doc/libs/1_83_0/libs/json/doc/html/json/allocators/storage_ptr.html //! //! IMPLEMENTATION DECISIONS:\n -//! Use a fixed size buffer for the allocator for performance reasons //! //! Retain documents created to ensure that the associated memory allocator exists for the documents //! lifetime @@ -58,16 +78,12 @@ class CBoostJsonPoolAllocator { json::storage_ptr& get() { return m_JsonStoragePointer; } private: - //! Size of the fixed buffer to allocate for parsing JSON - static const size_t FIXED_BUFFER_SIZE = 4096; - -private: - //! fixed size memory buffer used to optimize allocator performance - unsigned char m_FixedBuffer[FIXED_BUFFER_SIZE]; - //! storage pointer to use for allocating boost::json objects - json::storage_ptr m_JsonStoragePointer{ - json::make_shared_resource(m_FixedBuffer)}; + //! We use a custom resource allocator for more predictable + //! and timely allocation/de-allocations, see + //! https://www.boost.org/doc/libs/1_83_0/libs/json/doc/html/json/allocators/storage_ptr.html#json.allocators.storage_ptr.user_defined_resource + //! for more details. + json::storage_ptr m_JsonStoragePointer{json::make_shared_resource()}; //! Container used to persist boost::json documents TDocumentPtrVec m_JsonDocumentStore; diff --git a/include/core/CBoostJsonWriterBase.h b/include/core/CBoostJsonWriterBase.h index 1cd9a76c0e..49becdbd4b 100644 --- a/include/core/CBoostJsonWriterBase.h +++ b/include/core/CBoostJsonWriterBase.h @@ -24,10 +24,7 @@ #include #include -#include -#include #include -#include #include namespace json = boost::json; @@ -134,6 +131,14 @@ class CBoostJsonWriterBase { return this->getAllocator()->get(); } + void releaseAllocator(const std::string& allocatorName) { + if (m_AllocatorCache.find(allocatorName) != m_AllocatorCache.end()) { + TPoolAllocatorPtr allocator = m_AllocatorCache[allocatorName]; + allocator.reset(); + m_AllocatorCache.erase(allocatorName); + } + } + bool isComplete() const { bool ret = m_Levels.empty() || m_Levels.top() == 0; return ret; diff --git a/include/core/CScopedBoostJsonPoolAllocator.h b/include/core/CScopedBoostJsonPoolAllocator.h index 6b29f0e4f9..5c7dbdd41d 100644 --- a/include/core/CScopedBoostJsonPoolAllocator.h +++ b/include/core/CScopedBoostJsonPoolAllocator.h @@ -12,6 +12,7 @@ #define INCLUDED_ml_core_CScopedBoostJsonPoolAllocator_h #include +#include namespace ml { namespace core { @@ -31,14 +32,18 @@ class CScopedBoostJsonPoolAllocator { //! \p allocatorName Unique identifier for the allocator //! \p jsonOutputWriter JSON output writer that will make use of the allocator CScopedBoostJsonPoolAllocator(const std::string& allocatorName, T& writer) - : m_Writer(writer) { + : m_Writer(writer), m_AllocatorName(allocatorName) { m_Writer.pushAllocator(allocatorName); } - ~CScopedBoostJsonPoolAllocator() { m_Writer.popAllocator(); } + ~CScopedBoostJsonPoolAllocator() { + m_Writer.popAllocator(); + m_Writer.releaseAllocator(m_AllocatorName); + } private: T& m_Writer; + std::string m_AllocatorName; }; } } diff --git a/lib/api/CJsonOutputWriter.cc b/lib/api/CJsonOutputWriter.cc index 2a7a987c23..44e5a5699d 100644 --- a/lib/api/CJsonOutputWriter.cc +++ b/lib/api/CJsonOutputWriter.cc @@ -850,6 +850,10 @@ void CJsonOutputWriter::pushAllocator(const std::string& allocatorName) { m_Writer.pushAllocator(allocatorName); } +void CJsonOutputWriter::releaseAllocator(const std::string& allocatorName) { + m_Writer.releaseAllocator(allocatorName); +} + void CJsonOutputWriter::popAllocator() { m_Writer.popAllocator(); }