Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use boost::pool library to create a custom memory allocator

I am new to boost and I want to know how exactly the boost::pool libraries can help me in creating a custom memory allocator. And I have two vector of struct objects. First vector is of structure type A, while second vector is of structure type B. How can I reuse the memory allocated to the first vector to the second vector.

like image 748
R Parthiban Avatar asked Feb 14 '18 08:02

R Parthiban


Video Answer


1 Answers

Boost Pool is a library that defines a few allocator types.

Obviously, the focus of the library is to provide Pool Allocators.

Pool Allocators shine when you allocate objects of identical size.

Note If your structure A and structure B aren't identical/very similar size you may not like this design assumption.

The allocators provided by the framework work with singleton pools, and they differentiate on the size of your container value_type. That's a bit inflexible if you want to reuse or even share the pool between different value-types. Also, singleton pools can be inflexible and imply thread-safety costs.

So, I wanted to see whether I could whip up the simplest allocator that alleviates some of these issues.

I used the source to boost::pool_alloc and the cppreference example as inspiration, and then did some testing and memory profiling.

A More Flexible Stateful Allocator

Here's the simplest pool allocator I could think of:

using Pool = boost::pool<boost::default_user_allocator_malloc_free>;

template <typename T> struct my_pool_alloc {
    using value_type = T;

    my_pool_alloc(Pool& pool) : _pool(pool) {
        assert(pool_size() >= sizeof(T));
    }

    template <typename U>
    my_pool_alloc(my_pool_alloc<U> const& other) : _pool(other._pool) {
        assert(pool_size() >= sizeof(T));
    }

    T *allocate(const size_t n) {
        T* ret = static_cast<T*>(_pool.ordered_malloc(n));
        if (!ret && n) throw std::bad_alloc();
        return ret;
    }

    void deallocate(T* ptr, const size_t n) {
        if (ptr && n) _pool.ordered_free(ptr, n);
    }

    // for comparing
    size_t pool_size() const { return _pool.get_requested_size(); }

  private:
    Pool& _pool;
};

template <class T, class U> bool operator==(const my_pool_alloc<T> &a, const my_pool_alloc<U> &b) { return a.pool_size()==b.pool_size(); }
template <class T, class U> bool operator!=(const my_pool_alloc<T> &a, const my_pool_alloc<U> &b) { return a.pool_size()!=b.pool_size(); }

Notes:

  • This allocator is stateful, and thus requires container implementations that allow them (such as Boost Container, Boost MultiIndex). In theory, all C++11-compliant standard libraries should also support them
  • The comparisons should guide the containers to swap/copy the allocator or not. This is not an area I've though much about and the chosen approach to mark all pools of different requested-sizes different might be inadequate for some.

Sample, Tests

On my compilers it works for both std::vector and Boost's vector:

  • Live On Coliru - with GCC std::vector
  • Live On Coliru - with GCC boost::container::vector
  • Live On Coliru - with Clang std::vector
  • Live On Coliru - with Clang boost::container::vector

All runs are leak-free and ubsan/asan clean.

Note how we re-use the same pool with different containers of different struct sizes, and how we can even use it with multiple live containers at a time, provided that the element types fit in the request size (32)

struct A { char data[7];  };
struct B { char data[29]; };

int main() {
    //using boost::container::vector;
    using std::vector;

    Pool pool(32); // 32 should fit both sizeof(A) and sizeof(B)
    {
        vector<A, my_pool_alloc<A> > v(1024, pool);
        v.resize(20480);
    };

    // pool.release_memory();

    {
        vector<B, my_pool_alloc<B> > v(1024, pool);
        v.resize(20480);
    }

    // pool.release_memory();

    // sharing the pool between multiple live containers
    {
        vector<A, my_pool_alloc<A> > v(512, pool);
        vector<B, my_pool_alloc<B> > w(512, pool);
        v.resize(10240);
        w.resize(10240);
    };
}

Profiling

Using Valgrind's Memory profiler shows, with the release_memory lines commented out as shown:

enter image description here

When commenting in the release_memory() calls:

enter image description here

I hope this looks like the thing you wanted.

Further Ideas: Simple Segregated Storage

This allocator uses the existing pool which delegate back to malloc/free to allocate memory on demand. To use it with a fixed "realm", you might prefer using simple_segregated_storage directly. This article looks like a good starter https://theboostcpplibraries.com/boost.pool

like image 68
sehe Avatar answered Oct 27 '22 01:10

sehe