c++c++17allocatorc++pmr

How the synchronized_pool_resource actually works?


I am studying about the polymorphic memory allocation in C++17. I modified an example which uses monotonic_buffer_resource for vector allocations to use a synchronized_pool_resource. I detected a strange behavior. Specifically , there are many memory allocations , just for two additions in the vector. I did not run benchmarks but I think that this a huge penalty for the performance

The program was compiled using the O2 g++ -std=c++17 -O2 -Wall -pedantic

Below is the code

class debug_resource : public std::pmr::memory_resource {

public:
    explicit debug_resource(std::string name,
        std::pmr::memory_resource* up = std::pmr::get_default_resource())
        : _name{ std::move(name) }, _upstream{ up }
    { }

    void* do_allocate(size_t bytes, size_t alignment) override {
        std::cout << _name << " do_allocate(): " << bytes << '\n';
        void* ret = _upstream->allocate(bytes, alignment);
        return ret;
    }
    void do_deallocate(void* ptr, size_t bytes, size_t alignment) override {
        std::cout << _name << " do_deallocate(): " << bytes << '\n';
        _upstream->deallocate(ptr, bytes, alignment);
    }
    bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
        return this == &other;
    }

private:
    std::string _name;
    std::pmr::memory_resource* _upstream;
};
int main()
{
  
    debug_resource default_dbg{ "default" };
    std::pmr::synchronized_pool_resource pool(&default_dbg);
  //  debug_resource dbg{ "pool", &pool };
    std::pmr::vector<std::string> strings{ &pool };

   strings.emplace_back("Hello Short String");
   strings.emplace_back("Hello Short String 2");
}

The console output is the following

default do_allocate(): 32
default do_allocate(): 528
default do_allocate(): 32
default do_allocate(): 528
default do_allocate(): 1000
default do_allocate(): 192
default do_allocate(): 968
default do_allocate(): 192

default do_deallocate(): 528
default do_deallocate(): 32
default do_deallocate(): 1000
default do_deallocate(): 192
default do_deallocate(): 968
default do_deallocate(): 192
default do_deallocate(): 528
default do_deallocate(): 32


Solution

  • The answer is in the function description: https://en.cppreference.com/w/cpp/memory/synchronized_pool_resource

    It consists of a collection of pools that serves request for different block sizes. Each pool manages a collection of chunks that are then divided into blocks of uniform size.

    Calls to do_allocate are dispatched to the pool serving the smallest blocks accommodating the requested size.

    Exhausting memory in the pool causes the next allocation request for that pool to allocate an additional chunk of memory from the upstream allocator to replenish the pool. The chunk size obtained increases geometrically.

    The largest block size and maximum chunk size may be tuned by passing a std::pmr::pool_options struct to its constructor.

    So a pool is actually a collection of memory blocks. And this collection is increased when necessary. Hence multiple allocations.

    To decrease the number of allocations you can try playing with std::pmr::pool_options.