docs/classtf_1_1ObjectPool.html
| | Taskflow: A General-purpose Task-parallel Programming System |
Loading...
Searching...
No Matches
Public Member Functions | List of all members
tf::ObjectPool< T, H, LogSize > Class Template Reference
sharded fixed-size object allocator with a lock-free hot path More...
#include <taskflow/utility/object_pool.hpp>
|
|
| | ObjectPool ()=default |
| | constructs the allocator with 2^LogSize empty shards
|
| |
| | ObjectPool (const ObjectPool &)=delete |
| | disabled copy constructor
|
| |
| ObjectPool & | operator= (const ObjectPool &)=delete |
| | disabled copy assignment operator
|
| |
| | ~ObjectPool ()=default |
| | destroys the allocator and releases all backing memory to upstream
|
| |
| template<typename... Args> |
| T * | animate (Args &&... args) |
| | constructs an object of type T in the pool and returns a pointer
|
| |
| void | recycle (T *obj) |
| | destructs the object and returns its storage to the pool
|
| |
| void | release () |
| | returns all recycled blocks and backing memory to the system allocator
|
| |
template<typename T, typename H = TaggedHead128, size_t LogSize = 5>
class tf::ObjectPool< T, H, LogSize >
sharded fixed-size object allocator with a lock-free hot path
Template Parameters
| T | object type to allocate | | H | tagged-pointer policy that controls the free-stack head representation. The type must provide:
pointer_type — typedef for the address representationtag_type — typedef for the ABA counter representationget_ptr() — returns the stored address as pointer_typeget_tag() — returns the ABA counter as tag_typeH(pointer_type, tag_type) — two-argument constructor Built-in choices:|
| LogSize | log2 of the number of shards (default 5, giving 32 shards); must be in [1, 15] to fit the shard index in a uint16_t |
ObjectPool is a high-performance allocator for a single fixed-size type T, designed for concurrent task-parallel workloads where objects are frequently created and destroyed across many threads.
Internally, allocations are distributed across 2^LogSize independent shards. Each shard maintains two independent components (separated by cache lines to prevent false sharing):
Hot Path (99% of operations): A lock-free Treiber stack of recycled blocks. When tf::ObjectPool::animate is called, it tries to pop a recycled block from this stack with a single atomic CAS. On success, the block is reused with no mutex acquisition. Blocks returned by tf::ObjectPool::recycle are pushed back onto this stack without acquiring any mutex.
Cold Path (1% of operations): A std::pmr::synchronized_pool_resource as backing storage for fresh block allocations. This mutex-protected pool is only touched when the shard's hot-path stack is empty. When accessed, it allocates a whole chunk (configured to hold up to 1024 blocks via max_blocks_per_chunk = 1024), amortizing the synchronization cost: one mutex acquisition yields ~1024 blocks for the hot path.
The tagged-pointer policy H attaches a version counter to each free-stack head to prevent the ABA problem. This counter increments on every push and pop, making ABA wrap-around effectively impossible under realistic workloads. Shards are aligned to the cache line size to eliminate false sharing between concurrent threads accessing different shards' hot-path stacks.
// default: TaggedHead128, 32 shards
tf::ObjectPool<MyTask> pool;
// lock-free on all 64-bit platforms (default PtrBits=48), 32 shards
tf::ObjectPool<MyTask, 5, tf::TaggedHead64<>> pool64;
// construct a MyTask in the pool, forwarding constructor arguments
MyTask* t = pool.animate(arg1, arg2);
// ... use t ...
// destruct and return storage to the pool for reuse
pool.recycle(t);
sharded fixed-size object allocator with a lock-free hot path
Definition object_pool.hpp:408
T * animate(Args &&... args)
constructs an object of type T in the pool and returns a pointer
Definition object_pool.hpp:579
void recycle(T *obj)
destructs the object and returns its storage to the pool
Definition object_pool.hpp:621
NoteAll pointers returned by tf::ObjectPool::animate must be passed to tf::ObjectPool::recycle before the allocator is destroyed. Destroying the allocator with live objects is undefined behavior.Two-Level Freelist Design
The combination of lock-free and mutex-protected freelists is deliberate: recycled blocks remain on the lock-free stack indefinitely, avoiding mutex costs on every allocation. The backing pool's internal freelist is rarely used directly because blocks do not call deallocate() in the normal hot path — they stay on the lock-free stack for immediate reuse. This design trades chunk-level memory reuse efficiency for atomic-fast allocation on the hot path, which is the right trade-off for task-parallel workloads where the hot path is hit millions of times.
Chunk Amortization
When the hot-path stack is empty, a single std::pmr::synchronized_pool_resource::allocate call acquires a mutex and either reuses a chunk or allocates a new one from the system allocator. With max_blocks_per_chunk = 1024, one mutex acquisition amortizes to ~1024 subsequent lock-free pops, yielding negligible mutex overhead (roughly 0.001 mutex cost per allocation).
template<typename T, typename H = TaggedHead128, size_t LogSize = 5>
|
| tf::ObjectPool< T, H, LogSize >::ObjectPool | ( | | ) | |
| default |
constructs the allocator with 2^LogSize empty shards
Each shard is default-constructed with an empty free stack and an uninitialized backing pool. No memory is allocated from the OS until the first call to tf::ObjectPool::animate.
template<typename T, typename H = TaggedHead128, size_t LogSize = 5>
|
| tf::ObjectPool< T, H, LogSize >::ObjectPool | ( | const ObjectPool< T, H, LogSize > & | | ) | |
| delete |
disabled copy constructor
ObjectPool owns its shards and backing memory; copying is not meaningful. Declare the allocator as a global or long-lived member and share it by reference or pointer.
template<typename T, typename H = TaggedHead128, size_t LogSize = 5>
|
| tf::ObjectPool< T, H, LogSize >::~ObjectPool | ( | | ) | |
| default |
destroys the allocator and releases all backing memory to upstream
The destructor of each shard's std::pmr::synchronized_pool_resource returns all allocated chunks to the system allocator, including memory backing blocks that are currently on the free stack. No per-block destructor is called; callers are responsible for recycling all live objects before destroying the allocator.
template<typename T, typename H = TaggedHead128, size_t LogSize = 5>
template<typename... Args>
|
| T * tf::ObjectPool< T, H, LogSize >::animate | ( | Args &&... | args | ) | |
| inlinenodiscard |
constructs an object of type T in the pool and returns a pointer
Template Parameters
| Args | constructor argument types |
Parameters
| args | arguments forwarded to the constructor of T |
Returnspointer to the newly constructed T; never null
On the hot path, animate pops a previously recycled block from the shard's lock-free free stack and constructs T in it via std::construct_at, with no mutex acquisition. On a cache miss (empty free stack), a fresh block is carved from the shard's backing std::pmr::synchronized_pool_resource, which amortizes system allocation cost over chunks of up to 1024 blocks.
Allocations are distributed across shards via a per-thread round-robin counter seeded from the thread ID hash, balancing load with zero shared state after initialization.
tf::ObjectPool<MyTask> pool;
// default-construct
MyTask* t1 = pool.animate();
// construct with arguments
MyTask* t2 = pool.animate(42, "hello");
pool.recycle(t1);
pool.recycle(t2);
NoteThe returned pointer must eventually be passed to tf::ObjectPool::recycle. Discarding it without recycling leaks both the object's resources and the underlying block.
template<typename T, typename H = TaggedHead128, size_t LogSize = 5>
|
| void tf::ObjectPool< T, H, LogSize >::recycle | ( | T * | obj | ) | |
| inline |
destructs the object and returns its storage to the pool
Parameters
| obj | pointer to a T previously returned by tf::ObjectPool::animate, or nullptr (no-op) |
recycle calls the destructor of *obj via std::destroy_at, then pushes the underlying block onto its shard's lock-free free stack without acquiring any mutex. The block becomes immediately available for the next call to tf::ObjectPool::animate on any thread.
The correct shard is identified via the pool_id stored in the block header, so recycle may be called from any thread regardless of which thread called animate.
tf::ObjectPool<MyTask> pool;
MyTask* t = pool.animate(arg1, arg2);
// ... use t ...
pool.recycle(t); // destructor called here; memory returned to pool
t = nullptr; // pointer is now dangling; do not dereference
NotePassing a pointer not obtained from this allocator is undefined behavior. After recycle returns, obj is a dangling pointer and must not be dereferenced.
template<typename T, typename H = TaggedHead128, size_t LogSize = 5>
|
| void tf::ObjectPool< T, H, LogSize >::release | ( | | ) | |
| inline |
returns all recycled blocks and backing memory to the system allocator
release calls std::pmr::synchronized_pool_resource::release on each shard's backing pool, returning all chunks to the upstream system allocator in one shot, then atomically resets each shard's free stack to null. This is an O(1) operation per shard — no per-block work is performed because the backing pool owns memory at the chunk level and frees entire chunks regardless of how many individual blocks were returned to it.
After this call the allocator is in the same state as after construction: empty free stacks, no memory held from the OS.
This method is optional and is not required before destruction. It is useful for reclaiming pool memory between distinct workload phases without destroying the allocator itself.
tf::ObjectPool<MyTask> pool;
// --- phase 1 ---
for (auto& task : phase1_tasks) {
MyTask* t = pool.animate(task);
// ... run t ...
pool.recycle(t);
}
pool.release(); // return OS memory before phase 2 begins
// --- phase 2 ---
for (auto& task : phase2_tasks) {
MyTask* t = pool.animate(task);
// ... run t ...
pool.recycle(t);
}
void release()
returns all recycled blocks and backing memory to the system allocator
Definition object_pool.hpp:670
NoteAll live objects must be recycled before calling release. Calling release while objects are still alive is undefined behavior because the backing memory they reside in is freed.
The documentation for this class was generated from the following file:
taskflow/utility/object_pool.hpp
Maintained by Dr. Tsung-Wei Huang — Generated by 1.13.1