At 09:25 23/03/2019, Francesco wrote:
Yes I know about std::make_shared<> and indeed I was using that solution before implementing the memory pool based on intrusive pointers. The problem is that in my workload I have several threads that need to get a smart pointer to a fairly-big object every time they process input data and they will release it later.
Which brings up another point; your current implementation is not thread-safe. Which is fine, but you should probably mention that explicitly in your readme. A very common worker pattern is to have all allocations on one thread and all deallocations on another thread, and that wouldn't work as-is.
Now allocating with std::make_shared<>Â a new object on every input data was causing my threads to do lots of mallocs/sec and that became the performace limiting factor. The use of a memory pool instead allows me to do a very large memory allocation from time to time (in blocks of e.g. 1024 items) and still have each single item of the memory pool independently refcounted.
Yes, I realise that. It still could be possible to have individual node allocations strung up into a free list rather than requiring a larger contiguous allocation. But you're correct that arena allocation and intrusive_ptr is probably more efficient in the end.
My first implementation of the memory pool indeed was using C++11 perfect forwarding to call the ctor/dtor of type T when the item was pulled out the pool or was returning to it. I later removed such feature because I realized that calling the ctor of the items to recycle produced issues with classes having multiple inheritance IIRC. I don't think it's safe and sane to call the ctor of the same instance multiple times...Â
Calls must be paired -- you allocate aligned uninitialised storage (aligned_storage), then placement-new to call the constructor on that storage, then eventually call the destructor, and then later you can placement-new again. You must never call the constructor twice in a row or the destructor twice in a row (unless it's trivial), but alternating is fine. Again, this is how things like optional and variant work internally -- and since they do that part for you, it can be useful to re-use it rather than re-implementing it.