Add custom datastructure for local chunk cache
This commit adds a datastructure that provides efficient single-thread stack behaviour, while allowing other threads to steal the whole contents.
This commit is contained in:
committed by
Matthew Parkinson
parent
72ccb23d02
commit
3407347925
72
src/ds/spmcstack.h
Normal file
72
src/ds/spmcstack.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "aba.h"
|
||||
#include "ptrwrap.h"
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
/**
|
||||
* Concurrent Stack
|
||||
*
|
||||
* This stack supports the following clients
|
||||
* (push|pop)* || pop_all* || ... || pop_all*
|
||||
*
|
||||
* That is a single thread that can do push and pop, and other threads
|
||||
* that do pop_all. pop_all if it returns a value, returns all of the
|
||||
* stack, however, it may return nullptr if it races with either a push
|
||||
* or a pop.
|
||||
*
|
||||
* The primary use case is single-threaded access, where other threads
|
||||
* can attempt to steal all the values.
|
||||
*/
|
||||
template<class T>
|
||||
class SPMCStack
|
||||
{
|
||||
private:
|
||||
alignas(CACHELINE_SIZE) std::atomic<T*> stack{};
|
||||
|
||||
public:
|
||||
constexpr SPMCStack() = default;
|
||||
|
||||
void push(T* item)
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<decltype(T::next), std::atomic<T*>>::value,
|
||||
"T->next must be an std::atomic<T*>");
|
||||
|
||||
return push(item, item);
|
||||
}
|
||||
|
||||
void push(T* first, T* last)
|
||||
{
|
||||
T* old_head = stack.exchange(nullptr, std::memory_order_relaxed);
|
||||
last->next.store(old_head, std::memory_order_relaxed);
|
||||
// Assume stays null as not allowed to race with pop or other pushes.
|
||||
SNMALLOC_ASSERT(stack.load() == nullptr);
|
||||
stack.store(first, std::memory_order_release);
|
||||
}
|
||||
|
||||
T* pop()
|
||||
{
|
||||
if (stack.load(std::memory_order_relaxed) == nullptr)
|
||||
return nullptr;
|
||||
T* old_head = stack.exchange(nullptr);
|
||||
if (unlikely(old_head == nullptr))
|
||||
return nullptr;
|
||||
|
||||
auto next = old_head->next.load(std::memory_order_relaxed);
|
||||
|
||||
// Assume stays null as not allowed to race with pop or other pushes.
|
||||
SNMALLOC_ASSERT(stack.load() == nullptr);
|
||||
|
||||
stack.store(next, std::memory_order_release);
|
||||
|
||||
return old_head;
|
||||
}
|
||||
|
||||
T* pop_all()
|
||||
{
|
||||
return stack.exchange(nullptr);
|
||||
}
|
||||
};
|
||||
} // namespace snmalloc
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ds/mpmcstack.h"
|
||||
#include "../ds/spmcstack.h"
|
||||
#include "../mem/metaslab.h"
|
||||
#include "../mem/sizeclasstable.h"
|
||||
#include "../pal/pal_ds.h"
|
||||
@@ -55,9 +56,7 @@ namespace snmalloc
|
||||
/**
|
||||
* Stack of slabs that have been returned for reuse.
|
||||
*/
|
||||
ModArray<
|
||||
NUM_SLAB_SIZES,
|
||||
ModArray<NUM_EPOCHS, MPMCStack<ChunkRecord, RequiresInit>>>
|
||||
ModArray<NUM_SLAB_SIZES, ModArray<NUM_EPOCHS, SPMCStack<ChunkRecord>>>
|
||||
chunk_stack;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user