Current benchmarks of the ModernThreadSafeLinkedStack show significant performance degradation compared to the basic mutex-wrapped std::deque implementation. Even though the linked-list approach minimizes lock-holding time by constructing nodes outside the critical section, the overhead of frequent heap allocations (new/delete) and poor cache locality makes it a bottleneck.
Benchmark Context
Testing on an 8-core CPU with a 4 Producer / 4 Consumer load (100k items each) yielded the following results for the Heavy Payload (1KB) category:
| Implementation |
Time (ms) |
Throughput (MOps/s) |
| Basic Mutex Stack (deque) |
154.82 |
5.167 |
| Linked List Stack |
187.21 |
4.273 |
| SharedPtr Stack |
285.93 |
2.798 |
The Problem
- Allocation Overhead: Every
push operation triggers a call to the global allocator for a new Node.
- Memory Fragmentation: Standard heap allocation leads to scattered nodes in memory, resulting in frequent CPU cache misses during stack traversal.
- SharedPtr Control Block: Using
std::make_shared adds an additional allocation for the control block, further slowing down the process.
Proposed Solution: Boost.Pool Integration
I propose replacing the standard std::allocator with boost::fast_pool_allocator (from the Boost.Pool library) for node management.
Key Improvements:
- Chunk Allocation: Pre-allocate memory blocks to handle multiple nodes at once.
- O(1) Allocation: Significantly faster node creation/destruction.
- Enhanced Cache Locality: Nodes will be stored contiguously in memory pages.
- Reduced Contention: Move to a
thread_local pool strategy to eliminate allocator-level locking.
Tasks
Current benchmarks of the
ModernThreadSafeLinkedStackshow significant performance degradation compared to the basic mutex-wrappedstd::dequeimplementation. Even though the linked-list approach minimizes lock-holding time by constructing nodes outside the critical section, the overhead of frequent heap allocations (new/delete) and poor cache locality makes it a bottleneck.Benchmark Context
Testing on an 8-core CPU with a 4 Producer / 4 Consumer load (100k items each) yielded the following results for the Heavy Payload (1KB) category:
The Problem
pushoperation triggers a call to the global allocator for a newNode.std::make_sharedadds an additional allocation for the control block, further slowing down the process.Proposed Solution: Boost.Pool Integration
I propose replacing the standard
std::allocatorwithboost::fast_pool_allocator(from the Boost.Pool library) for node management.Key Improvements:
thread_localpool strategy to eliminate allocator-level locking.Tasks
boost::fast_pool_allocatorin a concurrent environment.PoolDeleterforstd::unique_ptr<Node>.ModernThreadSafeLinkedStack::pushto usestd::allocate_sharedwith the pool allocator.std::dequebaseline.