mesytec-mnode/external/taskflow-3.8.0/taskflow/utility/object_pool.hpp

775 lines
19 KiB
C++
Raw Normal View History

2025-01-04 01:25:05 +01:00
// 2020/03/13 - modified by Tsung-Wei Huang
// - fixed bug in aligning memory
//
// 2020/02/02 - modified by Tsung-Wei Huang
// - new implementation motivated by Hoard
//
// 2019/07/10 - modified by Tsung-Wei Huang
// - replace raw pointer with smart pointer
//
// 2019/06/13 - created by Tsung-Wei Huang
// - implemented an object pool class
#pragma once
#include <thread>
#include <atomic>
#include <mutex>
#include <vector>
#include <cassert>
#include <cstddef>
namespace tf {
#define TF_ENABLE_POOLABLE_ON_THIS \
template <typename T, size_t S> friend class ObjectPool; \
void* _object_pool_block
// Class: ObjectPool
//
// The class implements an efficient thread-safe object pool motivated
// by the Hoard memory allocator algorithm.
// Different from the normal memory allocator, object pool allocates
// only one object at a time.
//
// Internally, we use the following variables to maintain blocks and heaps:
// X: size in byte of a item slot
// M: number of items per block
// F: emptiness threshold
// B: number of bins per local heap (bin[B-1] is the full list)
// W: number of items per bin
// K: shrinkness constant
//
// Example scenario 1:
// M = 30
// F = 4
// W = (30+4-1)/4 = 8
//
// b0: 0, 1, 2, 3, 4, 5, 6, 7
// b1: 8, 9, 10, 11, 12, 13, 14, 15
// b2: 16, 17, 18, 19, 20, 21, 22, 23
// b3: 24, 25, 26, 27, 28, 29
// b4: 30 (anything equal to M)
//
// Example scenario 2:
// M = 32
// F = 4
// W = (32+4-1)/4 = 8
// b0: 0, 1, 2, 3, 4, 5, 6, 7
// b1: 8, 9, 10, 11, 12, 13, 14, 15
// b2: 16, 17, 18, 19, 20, 21, 22, 23
// b3: 24, 25, 26, 27, 28, 29, 30, 31
// b4: 32 (anything equal to M)
//
template <typename T, size_t S = 65536>
class ObjectPool {
// the data column must be sufficient to hold the pointer in freelist
constexpr static size_t X = (std::max)(sizeof(T*), sizeof(T));
//constexpr static size_t X = sizeof(long double) + std::max(sizeof(T*), sizeof(T));
//constexpr static size_t M = (S - offsetof(Block, data)) / X;
constexpr static size_t M = S / X;
constexpr static size_t F = 4;
constexpr static size_t B = F + 1;
constexpr static size_t W = (M + F - 1) / F;
constexpr static size_t K = 4;
static_assert(
S && (!(S & (S-1))), "block size S must be a power of two"
);
static_assert(
M >= 128, "block size S must be larger enough to pool at least 128 objects"
);
struct Blocklist {
Blocklist* prev;
Blocklist* next;
};
struct GlobalHeap {
std::mutex mutex;
Blocklist list;
};
struct LocalHeap {
std::mutex mutex;
Blocklist lists[B];
size_t u {0};
size_t a {0};
};
struct Block {
std::atomic<LocalHeap*> heap;
Blocklist list_node;
size_t i;
size_t u;
T* top;
// long double padding;
char data[S];
};
public:
/**
@brief constructs an object pool from a number of anticipated threads
*/
explicit ObjectPool(unsigned = std::thread::hardware_concurrency());
/**
@brief destructs the object pool
*/
~ObjectPool();
/**
@brief acquires a pointer to a object constructed from a given argument list
*/
template <typename... ArgsT>
T* animate(ArgsT&&... args);
/**
@brief recycles a object pointed by @c ptr and destroys it
*/
void recycle(T* ptr);
size_t num_bins_per_local_heap() const;
size_t num_objects_per_bin() const;
size_t num_objects_per_block() const;
size_t num_available_objects() const;
size_t num_allocated_objects() const;
size_t capacity() const;
size_t num_local_heaps() const;
size_t num_global_heaps() const;
size_t num_heaps() const;
float emptiness_threshold() const;
private:
const size_t _lheap_mask;
GlobalHeap _gheap;
std::vector<LocalHeap> _lheaps;
LocalHeap& _this_heap();
constexpr unsigned _next_pow2(unsigned n) const;
template <class P, class Q>
constexpr size_t _offset_in_class(const Q P::*member) const;
template <class P, class Q>
constexpr P* _parent_class_of(Q*, const Q P::*member);
template <class P, class Q>
constexpr P* _parent_class_of(const Q*, const Q P::*member) const;
constexpr Block* _block_of(Blocklist*);
constexpr Block* _block_of(const Blocklist*) const;
size_t _bin(size_t) const;
T* _allocate(Block*);
void _deallocate(Block*, T*);
void _blocklist_init_head(Blocklist*);
void _blocklist_add_impl(Blocklist*, Blocklist*, Blocklist*);
void _blocklist_push_front(Blocklist*, Blocklist*);
void _blocklist_push_back(Blocklist*, Blocklist*);
void _blocklist_del_impl(Blocklist*, Blocklist*);
void _blocklist_del(Blocklist*);
void _blocklist_replace(Blocklist*, Blocklist*);
void _blocklist_move_front(Blocklist*, Blocklist*);
void _blocklist_move_back(Blocklist*, Blocklist*);
bool _blocklist_is_first(const Blocklist*, const Blocklist*);
bool _blocklist_is_last(const Blocklist*, const Blocklist*);
bool _blocklist_is_empty(const Blocklist*);
bool _blocklist_is_singular(const Blocklist*);
template <typename C>
void _for_each_block_safe(Blocklist*, C&&);
template <typename C>
void _for_each_block(Blocklist*, C&&);
};
// ----------------------------------------------------------------------------
// ObjectPool definition
// ----------------------------------------------------------------------------
// Constructor
template <typename T, size_t S>
ObjectPool<T, S>::ObjectPool(unsigned t) :
//_heap_mask {(_next_pow2(t) << 1) - 1u},
//_heap_mask { _next_pow2(t<<1) - 1u },
//_heap_mask {(t << 1) - 1},
_lheap_mask { _next_pow2((t+1) << 1) - 1 },
_lheaps { _lheap_mask + 1 } {
_blocklist_init_head(&_gheap.list);
for(auto& h : _lheaps) {
for(size_t i=0; i<B; ++i) {
_blocklist_init_head(&h.lists[i]);
}
}
}
// Destructor
template <typename T, size_t S>
ObjectPool<T, S>::~ObjectPool() {
// clear local heaps
for(auto& h : _lheaps) {
for(size_t i=0; i<B; ++i) {
_for_each_block_safe(&h.lists[i], [] (Block* b) {
//std::free(b);
delete b;
});
}
}
// clear global heap
_for_each_block_safe(&_gheap.list, [] (Block* b) {
//std::free(b);
delete b;
});
}
// Function: num_bins_per_local_heap
template <typename T, size_t S>
size_t ObjectPool<T, S>::num_bins_per_local_heap() const {
return B;
}
// Function: num_objects_per_bin
template <typename T, size_t S>
size_t ObjectPool<T, S>::num_objects_per_bin() const {
return W;
}
// Function: num_objects_per_block
template <typename T, size_t S>
size_t ObjectPool<T, S>::num_objects_per_block() const {
return M;
}
// Function: emptiness_threshold
template <typename T, size_t S>
float ObjectPool<T, S>::emptiness_threshold() const {
return 1.0f/F;
}
// Function: num_global_heaps
template <typename T, size_t S>
size_t ObjectPool<T, S>::num_global_heaps() const {
return 1;
}
// Function: num_lheaps
template <typename T, size_t S>
size_t ObjectPool<T, S>::num_local_heaps() const {
return _lheaps.size();
}
// Function: num_heaps
template <typename T, size_t S>
size_t ObjectPool<T, S>::num_heaps() const {
return _lheaps.size() + 1;
}
// Function: capacity
template <typename T, size_t S>
size_t ObjectPool<T, S>::capacity() const {
size_t n = 0;
// global heap
for(auto p=_gheap.list.next; p!=&_gheap.list; p=p->next) {
n += M;
};
// local heap
for(auto& h : _lheaps) {
n += h.a;
}
return n;
}
// Function: num_available_objects
template <typename T, size_t S>
size_t ObjectPool<T, S>::num_available_objects() const {
size_t n = 0;
// global heap
for(auto p=_gheap.list.next; p!=&_gheap.list; p=p->next) {
n += (M - _block_of(p)->u);
};
// local heap
for(auto& h : _lheaps) {
n += (h.a - h.u);
}
return n;
}
// Function: num_allocated_objects
template <typename T, size_t S>
size_t ObjectPool<T, S>::num_allocated_objects() const {
size_t n = 0;
// global heap
for(auto p=_gheap.list.next; p!=&_gheap.list; p=p->next) {
n += _block_of(p)->u;
};
// local heap
for(auto& h : _lheaps) {
n += h.u;
}
return n;
}
// Function: _bin
template <typename T, size_t S>
size_t ObjectPool<T, S>::_bin(size_t u) const {
return u == M ? F : u/W;
}
// Function: _offset_in_class
template <typename T, size_t S>
template <class P, class Q>
constexpr size_t ObjectPool<T, S>::_offset_in_class(
const Q P::*member) const {
return (size_t) &( reinterpret_cast<P*>(0)->*member);
}
// C macro: parent_class_of(list_pointer, Block, list)
// C++: parent_class_of(list_pointer, &Block::list)
template <typename T, size_t S>
template <class P, class Q>
constexpr P* ObjectPool<T, S>::_parent_class_of(
Q* ptr, const Q P::*member
) {
return reinterpret_cast<P*>(reinterpret_cast<char*>(ptr) - _offset_in_class(member));
}
// Function: _parent_class_of
template <typename T, size_t S>
template <class P, class Q>
constexpr P* ObjectPool<T, S>::_parent_class_of(
const Q* ptr, const Q P::*member
) const {
return reinterpret_cast<P*>(reinterpret_cast<char*>(ptr) - _offset_in_class(member));
}
// Function: _block_of
template <typename T, size_t S>
constexpr typename ObjectPool<T, S>::Block*
ObjectPool<T, S>::_block_of(Blocklist* list) {
return _parent_class_of(list, &Block::list_node);
}
// Function: _block_of
template <typename T, size_t S>
constexpr typename ObjectPool<T, S>::Block*
ObjectPool<T, S>::_block_of(const Blocklist* list) const {
return _parent_class_of(list, &Block::list_node);
}
// Procedure: initialize a list head
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_init_head(Blocklist *list) {
list->next = list;
list->prev = list;
}
// Procedure: _blocklist_add_impl
// Insert a new entry between two known consecutive entries.
//
// This is only for internal list manipulation where we know
// the prev/next entries already!
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_add_impl(
Blocklist *curr, Blocklist *prev, Blocklist *next
) {
next->prev = curr;
curr->next = next;
curr->prev = prev;
prev->next = curr;
}
// list_push_front - add a new entry
// @curr: curr entry to be added
// @head: list head to add it after
//
// Insert a new entry after the specified head.
// This is good for implementing stacks.
//
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_push_front(
Blocklist *curr, Blocklist *head
) {
_blocklist_add_impl(curr, head, head->next);
}
// list_add_tail - add a new entry
// @curr: curr entry to be added
// @head: list head to add it before
//
// Insert a new entry before the specified head.
// This is useful for implementing queues.
//
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_push_back(
Blocklist *curr, Blocklist *head
) {
_blocklist_add_impl(curr, head->prev, head);
}
// Delete a list entry by making the prev/next entries
// point to each other.
//
// This is only for internal list manipulation where we know
// the prev/next entries already!
//
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_del_impl(
Blocklist * prev, Blocklist * next
) {
next->prev = prev;
prev->next = next;
}
// _blocklist_del - deletes entry from list.
// @entry: the element to delete from the list.
// Note: list_empty() on entry does not return true after this, the entry is
// in an undefined state.
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_del(Blocklist *entry) {
_blocklist_del_impl(entry->prev, entry->next);
entry->next = nullptr;
entry->prev = nullptr;
}
// list_replace - replace old entry by new one
// @old : the element to be replaced
// @curr : the new element to insert
//
// If @old was empty, it will be overwritten.
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_replace(
Blocklist *old, Blocklist *curr
) {
curr->next = old->next;
curr->next->prev = curr;
curr->prev = old->prev;
curr->prev->next = curr;
}
// list_move - delete from one list and add as another's head
// @list: the entry to move
// @head: the head that will precede our entry
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_move_front(
Blocklist *list, Blocklist *head
) {
_blocklist_del_impl(list->prev, list->next);
_blocklist_push_front(list, head);
}
// list_move_tail - delete from one list and add as another's tail
// @list: the entry to move
// @head: the head that will follow our entry
template <typename T, size_t S>
void ObjectPool<T, S>::_blocklist_move_back(
Blocklist *list, Blocklist *head
) {
_blocklist_del_impl(list->prev, list->next);
_blocklist_push_back(list, head);
}
// list_is_first - tests whether @list is the last entry in list @head
// @list: the entry to test
// @head: the head of the list
template <typename T, size_t S>
bool ObjectPool<T, S>::_blocklist_is_first(
const Blocklist *list, const Blocklist *head
) {
return list->prev == head;
}
// list_is_last - tests whether @list is the last entry in list @head
// @list: the entry to test
// @head: the head of the list
template <typename T, size_t S>
bool ObjectPool<T, S>::_blocklist_is_last(
const Blocklist *list, const Blocklist *head
) {
return list->next == head;
}
// list_empty - tests whether a list is empty
// @head: the list to test.
template <typename T, size_t S>
bool ObjectPool<T, S>::_blocklist_is_empty(const Blocklist *head) {
return head->next == head;
}
// list_is_singular - tests whether a list has just one entry.
// @head: the list to test.
template <typename T, size_t S>
bool ObjectPool<T, S>::_blocklist_is_singular(
const Blocklist *head
) {
return !_blocklist_is_empty(head) && (head->next == head->prev);
}
// Procedure: _for_each_block
template <typename T, size_t S>
template <typename C>
void ObjectPool<T, S>::_for_each_block(Blocklist* head, C&& c) {
Blocklist* p;
for(p=head->next; p!=head; p=p->next) {
c(_block_of(p));
}
}
// Procedure: _for_each_block_safe
// Iterate each item of a list - safe to free
template <typename T, size_t S>
template <typename C>
void ObjectPool<T, S>::_for_each_block_safe(Blocklist* head, C&& c) {
Blocklist* p;
Blocklist* t;
for(p=head->next, t=p->next; p!=head; p=t, t=p->next) {
c(_block_of(p));
}
}
// Function: _allocate
// allocate a spot from the block
template <typename T, size_t S>
T* ObjectPool<T, S>::_allocate(Block* s) {
if(s->top == nullptr) {
return reinterpret_cast<T*>(s->data + s->i++ * X);
}
else {
T* retval = s->top;
s->top = *(reinterpret_cast<T**>(s->top));
return retval;
}
}
// Procedure: _deallocate
template <typename T, size_t S>
void ObjectPool<T, S>::_deallocate(Block* s, T* ptr) {
*(reinterpret_cast<T**>(ptr)) = s->top;
s->top = ptr;
}
// Function: allocate
template <typename T, size_t S>
template <typename... ArgsT>
T* ObjectPool<T, S>::animate(ArgsT&&... args) {
//std::cout << "construct a new item\n";
// my logically mapped heap
LocalHeap& h = _this_heap();
Block* s {nullptr};
h.mutex.lock();
// scan the list of superblocks from the most full to the least full
int f = static_cast<int>(F-1);
for(; f>=0; f--) {
if(!_blocklist_is_empty(&h.lists[f])) {
s = _block_of(h.lists[f].next);
break;
}
}
// no superblock found
if(f == -1) {
// check heap 0 for a superblock
_gheap.mutex.lock();
if(!_blocklist_is_empty(&_gheap.list)) {
s = _block_of(_gheap.list.next);
//printf("get a superblock from global heap %lu\n", s->u);
assert(s->u < M && s->heap == nullptr);
f = static_cast<int>(_bin(s->u + 1));
_blocklist_move_front(&s->list_node, &h.lists[f]);
s->heap = &h; // must be within the global heap lock
_gheap.mutex.unlock();
h.u = h.u + s->u;
h.a = h.a + M;
}
// create a new block
else {
//printf("create a new superblock\n");
_gheap.mutex.unlock();
f = 0;
//s = static_cast<Block*>(std::malloc(sizeof(Block)));
s = new Block();
s->heap = &h;
s->i = 0;
s->u = 0;
s->top = nullptr;
_blocklist_push_front(&s->list_node, &h.lists[f]);
h.a = h.a + M;
}
}
// the superblock must have at least one space
//assert(s->u < M);
//printf("%lu %lu %lu\n", h.u, h.a, s->u);
//assert(h.u < h.a);
h.u = h.u + 1;
s->u = s->u + 1;
// take one item from the superblock
T* mem = _allocate(s);
int b = static_cast<int>(_bin(s->u));
if(b != f) {
//printf("move superblock from list[%d] to list[%d]\n", f, b);
_blocklist_move_front(&s->list_node, &h.lists[b]);
}
//std::cout << "s.i " << s->i << '\n'
// << "s.u " << s->u << '\n'
// << "h.u " << h.u << '\n'
// << "h.a " << h.a << '\n';
h.mutex.unlock();
//printf("allocate %p (s=%p)\n", mem, s);
new (mem) T(std::forward<ArgsT>(args)...);
mem->_object_pool_block = s;
return mem;
}
// Function: destruct
template <typename T, size_t S>
void ObjectPool<T, S>::recycle(T* mem) {
//Block* s = *reinterpret_cast<Block**>(
// reinterpret_cast<char*>(mem) - sizeof(Block**)
//);
//Block* s= *(reinterpret_cast<Block**>(mem) - O); // (mem) - 1
Block* s = static_cast<Block*>(mem->_object_pool_block);
mem->~T();
//printf("deallocate %p (s=%p) M=%lu W=%lu X=%lu\n", mem, s, M, W, X);
// here we need a loop because when we lock the heap,
// other threads may have removed the superblock to another heap
bool sync = false;
do {
LocalHeap* h = s->heap.load(std::memory_order_relaxed);
// the block is in global heap
if(h == nullptr) {
std::lock_guard<std::mutex> glock(_gheap.mutex);
if(s->heap == h) {
sync = true;
_deallocate(s, mem);
s->u = s->u - 1;
}
}
else {
std::lock_guard<std::mutex> llock(h->mutex);
if(s->heap == h) {
sync = true;
// deallocate the item from the superblock
size_t f = _bin(s->u);
_deallocate(s, mem);
s->u = s->u - 1;
h->u = h->u - 1;
size_t b = _bin(s->u);
if(b != f) {
//printf("move superblock from list[%d] to list[%d]\n", f, b);
_blocklist_move_front(&s->list_node, &h->lists[b]);
}
// transfer a mostly-empty superblock to global heap
if((h->u + K*M < h->a) && (h->u < ((F-1) * h->a / F))) {
for(size_t i=0; i<F; i++) {
if(!_blocklist_is_empty(&h->lists[i])) {
Block* x = _block_of(h->lists[i].next);
//printf("transfer a block (x.u=%lu/x.i=%lu) to the global heap\n", x->u, x->i);
assert(h->u > x->u && h->a > M);
h->u = h->u - x->u;
h->a = h->a - M;
x->heap = nullptr;
std::lock_guard<std::mutex> glock(_gheap.mutex);
_blocklist_move_front(&x->list_node, &_gheap.list);
break;
}
}
}
}
}
} while(!sync);
//std::cout << "s.i " << s->i << '\n'
// << "s.u " << s->u << '\n';
}
// Function: _this_heap
template <typename T, size_t S>
typename ObjectPool<T, S>::LocalHeap&
ObjectPool<T, S>::_this_heap() {
// here we don't use thread local since object pool might be
// created and destroyed multiple times
//thread_local auto hv = std::hash<std::thread::id>()(std::this_thread::get_id());
//return _lheaps[hv & _lheap_mask];
return _lheaps[
std::hash<std::thread::id>()(std::this_thread::get_id()) & _lheap_mask
];
}
// Function: _next_pow2
template <typename T, size_t S>
constexpr unsigned ObjectPool<T, S>::_next_pow2(unsigned n) const {
if(n == 0) return 1;
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;
return n;
}
} // end namespace tf --------------------------------------------------------