pool: correctly forward constructor parameters

This commit is contained in:
Danny Robson 2018-03-14 18:15:07 +11:00
parent bafe71b3ab
commit 7ce2f5454d
2 changed files with 119 additions and 48 deletions

120
pool.hpp
View File

@ -20,6 +20,7 @@
#include "nocopy.hpp" #include "nocopy.hpp"
#include "debug.hpp" #include "debug.hpp"
#include <atomic>
#include <cstdlib> #include <cstdlib>
#include <cstdint> #include <cstdint>
@ -29,35 +30,48 @@ namespace util {
/// non-POD types can be stored, but there are no guarantees for calling /// non-POD types can be stored, but there are no guarantees for calling
/// item destructors at pool destruction time. /// item destructors at pool destruction time.
template <typename T> template <typename T>
class pool : public nocopy { class pool {
protected: protected:
union node;
union alignas (T) node { union alignas (T) node {
node *_node; std::atomic<node*> next;
char _data[sizeof (T)]; char data[sizeof (T)];
}; };
node *m_head; // root address of allocation static_assert (std::atomic<node*>::is_always_lock_free);
node *m_next; // next available entry in the linked list
// root address of allocation. used in deletion at destruction time.
node* m_head;
// the next available entry in the linked list
std::atomic<node *> m_next;
// the total number of items that could be stored
const size_t m_capacity; const size_t m_capacity;
size_t m_size;
// the number of items currently stored.
std::atomic<size_t> m_size;
public: public:
pool (const pool&) = delete;
pool& operator= (const pool&) = delete;
pool (pool&&);
pool& operator= (pool&&);
explicit explicit
pool (unsigned int _capacity): pool (unsigned int _capacity):
m_capacity (_capacity), m_capacity (_capacity),
m_size (0u) m_size (0u)
{ {
static_assert (sizeof (T) >= sizeof (uintptr_t),
"pool<T>'s chained block system requires that T be at least pointer sized");
// allocate the memory and note the base address for deletion in destructor // allocate the memory and note the base address for deletion in destructor
m_next = m_head = new node[m_capacity]; // static_cast<node *> (operator new (sizeof (T) * m_capacity)); m_next = m_head = new node[m_capacity];
// initialise the linked list of nodes // build out a complete singly linked list from all the nodes.
for (unsigned int i = 0; i < m_capacity - 1; ++i) for (size_t i = 0; i < m_capacity - 1; ++i)
m_next[i]._node = m_next + i + 1; m_next[i].next = m_next + i + 1;
m_next[m_capacity - 1]._node = nullptr; m_next[m_capacity - 1].next = nullptr;
} }
~pool () ~pool ()
@ -68,69 +82,89 @@ namespace util {
} }
// Data management // Data management
template <typename ...Args> [[nodiscard]] T*
T* acquire (Args&... args) allocate (void)
{ {
// double check we have enough capacity left // double check we have enough capacity left
if (!m_next) if (!m_next)
throw std::bad_alloc (); throw std::bad_alloc ();
CHECK_LT (m_size, m_capacity); CHECK_LT (m_size, m_capacity);
// save what will become the next node shortly. it could be overwritten // unlink the current cursor
// in the constructor we're about to call. do {
node *newnext = m_next->_node; node* curr = m_next;
T *data = reinterpret_cast<T*> (m_next); node* soon = curr->next;
// try to construct the returnable object. if (m_next.compare_exchange_weak (curr, soon)) {
++m_size;
return reinterpret_cast<T*> (curr);
}
} while (1);
}
void
deallocate (T *base)
{
auto soon = reinterpret_cast<node*> (base);
do {
node *curr = m_next;
soon->next = curr;
if (m_next.compare_exchange_weak (curr, soon)) {
--m_size;
return;
}
} while (1);
}
template <typename ...Args>
T*
construct (Args &&...args)
{
auto ptr = allocate ();
try { try {
new (data) T (args...); return new (ptr) T (std::forward<Args> (args)...);
} catch (...) { } catch (...) {
// the constructor may have overwritten the node linkages before deallocate (ptr);
// throwing. fix this up before forwarding the exception.
m_next->_node = newnext;
throw; throw;
} }
// the object is valid. save the new linked list head and bump the
// stats for availability.
m_next = newnext;
m_size++;
return data;
} }
void release (T *data)
void
destroy (T *ptr)
{ {
CHECK_NEZ (m_size); ptr->~T ();
deallocate (ptr);
data->~T();
node *newnode = reinterpret_cast<node *> (data);
newnode->_node = m_next;
m_next = newnode;
m_size--;
} }
size_t capacity (void) const size_t capacity (void) const
{ {
return m_capacity; return m_capacity;
} }
size_t size (void) const size_t size (void) const
{ {
return m_size; return m_size;
} }
bool empty (void) const bool empty (void) const
{ {
return m_size == m_capacity; return m_size == m_capacity;
} }
// Indexing // Indexing
size_t index (const T*) const; size_t index (const T*) const;
T& operator[] (size_t idx) &; T& operator[] (size_t idx) &;
const T& operator[] (size_t idx) const&; const T& operator[] (size_t idx) const&;
}; };
} }
#endif #endif

View File

@ -14,7 +14,7 @@ check_single (util::TAP::logger &tap)
// Ensure a single element doesn't break the circular linked list // Ensure a single element doesn't break the circular linked list
util::pool<uint64_t> single(1); util::pool<uint64_t> single(1);
tap.expect_nothrow ([&] { tap.expect_nothrow ([&] {
single.release (single.acquire ()); single.deallocate (single.allocate ());
}, "single element acquire-release"); }, "single element acquire-release");
} }
@ -28,19 +28,19 @@ check_unique_ptr (util::TAP::logger &tap)
// Take all pointers out, checking they are unique, then replace for destruction. // Take all pointers out, checking they are unique, then replace for destruction.
while (!uintpool.empty ()) while (!uintpool.empty ())
uintset.insert (uintpool.acquire ()); uintset.insert (uintpool.allocate ());
tap.expect_eq (uintset.size (), uintpool.capacity (), "extracted maximum elements"); tap.expect_eq (uintset.size (), uintpool.capacity (), "extracted maximum elements");
for (auto i: uintset) for (auto i: uintset)
uintpool.release (i); uintpool.deallocate (i);
tap.expect_eq (uintpool.size (), 0u, "re-inserted maximum elements"); tap.expect_eq (uintpool.size (), 0u, "re-inserted maximum elements");
uintset.clear (); uintset.clear ();
// Do the above one more time to ensure that releasing works right // Do the above one more time to ensure that releasing works right
while (!uintpool.empty ()) while (!uintpool.empty ())
uintset.insert (uintpool.acquire ()); uintset.insert (uintpool.allocate ());
tap.expect_eq (uintset.size (), uintpool.capacity (), "re-extracted maximum elements"); tap.expect_eq (uintset.size (), uintpool.capacity (), "re-extracted maximum elements");
} }
@ -56,7 +56,7 @@ check_keep_value (util::TAP::logger &tap)
// Give every item a unique value // Give every item a unique value
for (unsigned int i = 0; i < uintpool.capacity (); ++i) { for (unsigned int i = 0; i < uintpool.capacity (); ++i) {
uint64_t *item = uintpool.acquire (); uint64_t *item = uintpool.allocate ();
*item = i; *item = i;
uintvector.push_back(item); uintvector.push_back(item);
@ -83,6 +83,42 @@ check_keep_value (util::TAP::logger &tap)
} }
//-----------------------------------------------------------------------------
void
check_keep_variadic_value (util::TAP::logger &tap)
{
struct foo_t {
foo_t (uint64_t _a, uint64_t _b, uint64_t _c, uint64_t _d):
a (_a),
b (_b),
c (_c),
d (_d)
{ ; }
uint64_t a, b, c, d;
};
util::pool<foo_t> alloc (512);
bool success = true;
for (uint64_t a = 0; a < 8; ++a)
for (uint64_t b = 0; b < 8; ++b)
for (uint64_t c = 0; c < 8; ++c) {
uint64_t d = (a << 24) | (b << 16) | (c << 8);
auto ptr = alloc.construct (a, b, c, d);
if (ptr->a != a || ptr->b != b || ptr->c != c || ptr->d != d) {
success = false;
goto done;
}
}
done:
tap.expect (success, "variadiac construction retains values");
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
int int
main (int, char **) main (int, char **)
@ -92,6 +128,7 @@ main (int, char **)
check_single (tap); check_single (tap);
check_unique_ptr (tap); check_unique_ptr (tap);
check_keep_value (tap); check_keep_value (tap);
check_keep_variadic_value (tap);
return tap.status (); return tap.status ();
} }