Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions include/api/CJsonOutputWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
40 changes: 28 additions & 12 deletions include/core/CBoostJsonPoolAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<custom_resource const*>(&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
Expand Down Expand Up @@ -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<json::monotonic_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<custom_resource>()};

//! Container used to persist boost::json documents
TDocumentPtrVec m_JsonDocumentStore;
Expand Down
11 changes: 8 additions & 3 deletions include/core/CBoostJsonWriterBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@
#include <boost/unordered_set.hpp>

#include <cmath>
#include <iomanip>
#include <iostream>
#include <memory>
#include <regex>
#include <stack>

namespace json = boost::json;
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions include/core/CScopedBoostJsonPoolAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#define INCLUDED_ml_core_CScopedBoostJsonPoolAllocator_h

#include <boost/json.hpp>
#include <string>

namespace ml {
namespace core {
Expand All @@ -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;
};
}
}
Expand Down
4 changes: 4 additions & 0 deletions lib/api/CJsonOutputWriter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down