How to reuse Stack Allocator in RapidJSON without reallocating memory

1k Views Asked by At

I'm running a single-threaded system in FreeRTOS with limited resources.

I already preallocate buffers for the RapidJSON allocators as so:

char            valueBuffer[2048];
char            parseBuffer[1024];
rapidjson::MemoryPoolAllocator<FreeRTOSRapidJSONAllocator> valueAllocator (valueBuffer, sizeof(valueBuffer))
rapidjson::MemoryPoolAllocator<FreeRTOSRapidJSONAllocator> parseAllocator (parseBuffer, sizeof(parseBuffer));

The issue I have is that every time one of the allocators is used, its size keeps on increasing (and allocating new memory if necessary) unless they are cleared. The problem with calling Clear() on the allocators is that Malloc is called again when the allocators are next resized, which I want to avoid.

Is there a way to simply reuse the existing preallocated memory, such as by setting the allocators' size back to zero, for example?

2

There are 2 best solutions below

0
On BEST ANSWER

I resolved this by creating a custom allocator. Essentially a copy of rapidjson::MemoryPoolAllocator with the addition of the following method:

void Reset()
{
    chunkHead_->size = 0;
    chunkHead_->next = 0;
}

Which should be called every time you're done with the last string that was parsed.

2
On

I needed to do the same, and thought I'd note what from reading the allocator code seems to be an alternative:

While I again create a char buffer[2048], I do not create and keep an allocator alongside it.

Rather, I create and delete an allocator anew when needed, while re-using the memory block. From reading the code of the allocator I see no Malloc so that should all be on the stack, no?

Edit - code example:

class MyClass
{
public: 
    myClass() : 
    _json_log_string_buffer(&_json_string_buffer_allocator, 
                            _json_buffer_size) {}

(...)
private:
    static constexpr int _json_buffer_size {4096};
    char _json_logger_buffer[_json_buffer_size];

    rapidjson::CrtAllocator _json_string_buffer_allocator;
    rapidjson::StringBuffer _json_log_string_buffer;
};

MyClass::log_to_json()
{
    rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator> 
    json_logger_allocator {_json_logger_buffer, 
                           sizeof(_json_logger_buffer)};

    rapidjson::Document json_doc(&json_logger_allocator);

    auto& allocator = json_doc.GetAllocator();

    json_doc.SetObject();

    // using it:
    json_doc.AddMember("id", id(), allocator);
    json_doc.AddMember("KEY", "VALUE", 
    allocator);
    (...)

    // Here, monitoring the size of allocator, it never goes 
    // beyond the 4096 on repeat invocations.
    // If I had it as a member, only creating once, it would grow.
    std::cout << "JSON allocator size: " << allocator.Size() << 
    ", capacity: " << allocator.Capacity() << std::endl;


    // Bonus point: I'm also using a rapidjson::StringBuffer.
    // From a first read it doesn't seem to re-allocate.
    // It doesn't use a MemoryPoolAllocator so I cannot do the 
    // same for it as I did for rapidjson::Document.
    _json_log_string_buffer.Clear();

    rapidjson::Writer<rapidjson::StringBuffer> 
    writer(_json_log_string_buffer);

    json_doc.Accept(writer);
    auto as_string = _json_log_string_buffer.GetString();

    // Using string with logger...
}