spinlock, pool: add
Signed-off-by: Simon Rozman <simon@rozman.si>
This commit is contained in:
parent
cc8a6eeee7
commit
bea4b5b408
@ -124,6 +124,7 @@
|
|||||||
<ClCompile Include="pch.cpp">
|
<ClCompile Include="pch.cpp">
|
||||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="pool.cpp" />
|
||||||
<ClCompile Include="ring.cpp" />
|
<ClCompile Include="ring.cpp" />
|
||||||
<ClCompile Include="sgml.cpp" />
|
<ClCompile Include="sgml.cpp" />
|
||||||
<ClCompile Include="stream.cpp" />
|
<ClCompile Include="stream.cpp" />
|
||||||
|
@ -42,6 +42,9 @@
|
|||||||
<ClCompile Include="watchdog.cpp">
|
<ClCompile Include="watchdog.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="pool.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="pch.hpp">
|
<ClInclude Include="pch.hpp">
|
||||||
|
@ -16,10 +16,12 @@
|
|||||||
#include <stdex/mapping.hpp>
|
#include <stdex/mapping.hpp>
|
||||||
#include <stdex/math.hpp>
|
#include <stdex/math.hpp>
|
||||||
#include <stdex/parser.hpp>
|
#include <stdex/parser.hpp>
|
||||||
|
#include <stdex/pool.hpp>
|
||||||
#include <stdex/progress.hpp>
|
#include <stdex/progress.hpp>
|
||||||
#include <stdex/ring.hpp>
|
#include <stdex/ring.hpp>
|
||||||
#include <stdex/sgml.hpp>
|
#include <stdex/sgml.hpp>
|
||||||
#include <stdex/socket.hpp>
|
#include <stdex/socket.hpp>
|
||||||
|
#include <stdex/spinlock.hpp>
|
||||||
#include <stdex/stream.hpp>
|
#include <stdex/stream.hpp>
|
||||||
#include <stdex/string.hpp>
|
#include <stdex/string.hpp>
|
||||||
#include <stdex/sys_info.hpp>
|
#include <stdex/sys_info.hpp>
|
||||||
@ -32,4 +34,5 @@
|
|||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <list>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
42
UnitTests/pool.cpp
Normal file
42
UnitTests/pool.cpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
Copyright © 2023 Amebis
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pch.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
#ifdef _WIN32
|
||||||
|
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace UnitTests
|
||||||
|
{
|
||||||
|
constexpr size_t capacity = 50;
|
||||||
|
|
||||||
|
TEST_CLASS(pool)
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TEST_METHOD(test)
|
||||||
|
{
|
||||||
|
using worker_t = unique_ptr<int>;
|
||||||
|
using pool_t = stdex::pool<worker_t>;
|
||||||
|
pool_t pool;
|
||||||
|
list<thread> workers;
|
||||||
|
for (auto n = thread::hardware_concurrency(); n--; ) {
|
||||||
|
workers.push_back(std::move(thread([](_Inout_ pool_t& pool)
|
||||||
|
{
|
||||||
|
for (size_t n = 10000; n--; ) {
|
||||||
|
worker_t el = move(pool.pop());
|
||||||
|
if (!el)
|
||||||
|
el.reset(new int(1));
|
||||||
|
pool.push(move(el));
|
||||||
|
}
|
||||||
|
}, ref(pool))));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& w : workers)
|
||||||
|
w.join();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
91
include/stdex/pool.hpp
Normal file
91
include/stdex/pool.hpp
Normal 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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
76
include/stdex/spinlock.hpp
Normal file
76
include/stdex/spinlock.hpp
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user