spinlock, pool: add

Signed-off-by: Simon Rozman <simon@rozman.si>
This commit is contained in:
2023-11-10 12:38:13 +01:00
parent cc8a6eeee7
commit bea4b5b408
6 changed files with 216 additions and 0 deletions

91
include/stdex/pool.hpp Normal file
View File

@@ -0,0 +1,91 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023 Amebis
*/
#pragma once
#include "compat.hpp"
#include "spinlock.hpp"
#include "windows.h"
#include <list>
#include <map>
#include <mutex>
namespace stdex
{
///
/// Per-NUMA pool of items
///
template <class T>
class pool
{
public:
#ifdef _WIN32
using numaid_t = USHORT;
#else
using numaid_t = int;
#endif
private:
struct numaentry_t {
mutable spinlock lock;
std::list<T> list;
};
mutable std::mutex m_mutex;
std::map<numaid_t, numaentry_t> m_available;
private:
static inline numaid_t numa_node()
{
#ifdef _WIN32
PROCESSOR_NUMBER Processor;
GetCurrentProcessorNumberEx(&Processor);
USHORT NodeNumber = 0;
return GetNumaProcessorNodeEx(&Processor, &NodeNumber) ? NodeNumber : 0;
#else
return numa_node_of_cpu(sched_getcpu());
#endif
}
inline numaentry_t& numa_entry(numaid_t numa = numa_node())
{
const std::lock_guard<std::mutex> guard(m_mutex);
return m_available[numa];
}
public:
///
/// Removes an item from the pool
///
/// \param[in] numa NUMA node to identify subpool to remove item from
///
/// \returns An item from the pool or default value if pool is empty
///
T pop(_In_ numaid_t numa = numa_node())
{
auto& ne = numa_entry(numa);
const std::lock_guard<spinlock> guard(ne.lock);
if (!ne.list.empty()) {
auto r = std::move(ne.list.front());
ne.list.pop_front();
return r;
}
return T();
}
///
/// Adds an item to the pool
///
/// \param[in] r Item to add
/// \param[in] numa NUMA node to identify subpool to add item to
///
void push(_Inout_ T&& r, _In_ numaid_t numa = numa_node())
{
auto& ne = numa_entry(numa);
const std::lock_guard<spinlock> guard(ne.lock);
ne.list.push_front(std::move(r));
}
};
}

View File

@@ -0,0 +1,76 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023 Amebis
*/
#pragma once
#ifdef _WIN32
#include "windows.h"
#include <intrin.h>
#endif
#include <atomic>
namespace stdex
{
///
/// Spin-lock
///
/// \sa [Correctly implementing a spinlock in C++](https://rigtorp.se/spinlock/)
///
class spinlock
{
private:
std::atomic<bool> m_lock = { false };
public:
///
/// Blocks until a lock can be acquired for the current execution agent (thread, process, task).
///
void lock() noexcept
{
for (;;) {
// Optimistically assume the lock is free on the first try
if (!m_lock.exchange(true, std::memory_order_acquire))
return;
// Wait for lock to be released without generating cache misses
while (m_lock.load(std::memory_order_relaxed)) {
// Issue X86 PAUSE or ARM YIELD instruction to reduce contention between
// hyper-threads
#if _M_ARM || _M_ARM64
__yield();
#elif _M_IX86 || _M_X64
_mm_pause();
#elif __aarch64__
__yield();
#elif __i386__ || __x86_64__
__builtin_ia32_pause();
#endif
}
}
}
///
/// Attempts to acquire the lock for the current execution agent (thread, process, task) without blocking.
///
/// \returns true if the lock was acquired, false otherwise
///
bool try_lock() noexcept
{
// First do a relaxed load to check if lock is free in order to prevent
// unnecessary cache misses if someone does while(!try_lock())
return
!m_lock.load(std::memory_order_relaxed) &&
!m_lock.exchange(true, std::memory_order_acquire);
}
///
/// Releases the non-shared lock held by the execution agent.
///
void unlock() noexcept
{
m_lock.store(false, std::memory_order_release);
}
};
}