16#include <sys/syscall.h>
18#if __has_include(<linux/mempolicy.h>)
19#include <linux/mempolicy.h>
41 std::size_t worker_index) {
42 NumaPlacement placement{mode, worker_index, std::nullopt, std::nullopt};
45 const auto node_index = worker_index % info.
topology.size();
46 const auto& node = info.
topology[node_index];
47 placement.node_id = node.id;
48 if (!node.cpus.empty()) {
49 const auto local_worker = worker_index / info.
topology.size();
50 placement.cpu_id = node.cpus[local_worker % node.cpus.size()];
57 if (cpu_id >= CPU_SETSIZE)
return false;
60 CPU_SET(cpu_id, &cpuset);
61 return pthread_setaffinity_np(pthread_self(),
sizeof(cpu_set_t), &cpuset) == 0;
69 if (!placement.cpu_id.has_value())
return placement.mode ==
NumaMode::off;
74 std::size_t workers) {
75 std::vector<NumaPlacement> placements;
76 placements.reserve(workers);
77 for (std::size_t i = 0; i < workers; ++i) {
84 std::optional<std::size_t> node_id,
85 std::size_t max_node_id)
noexcept {
86#if defined(__linux__) && defined(SYS_mbind) && defined(MPOL_BIND) && defined(MPOL_INTERLEAVE)
88 const std::size_t word_bits =
sizeof(
unsigned long) * 8U;
89 const std::size_t maxnode = max_node_id + 1U;
90 std::vector<unsigned long> mask((maxnode + word_bits - 1U) / word_bits, 0UL);
92 for (std::size_t node = 0; node <= max_node_id; ++node)
93 mask[node / word_bits] |= 1UL << (node % word_bits);
95 if (!node_id.has_value() || *node_id > max_node_id)
return false;
96 mask[*node_id / word_bits] |= 1UL << (*node_id % word_bits);
99 return ::syscall(SYS_mbind, address, bytes, policy, mask.data(), maxnode, 0UL) == 0;
101 (void)address; (void)bytes; (void)mode; (void)node_id; (void)max_node_id;
108 std::optional<std::size_t> node_id = std::nullopt)
noexcept {
110 if (bytes == 0)
return region;
111#if defined(__linux__)
112 void* memory = ::mmap(
nullptr, bytes, PROT_READ | PROT_WRITE,
113 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
114 if (memory == MAP_FAILED)
return region;
115 region.data = memory;
116 if (mode !=
NumaMode::off && info.native_support && !info.topology.empty()) {
117 std::size_t max_node_id = 0;
118 for (
const auto& node : info.topology)
if (node.id > max_node_id) max_node_id = node.id;
119 region.native_policy_applied =
linux_mbind_region(memory, bytes, mode, node_id, max_node_id);
128 if (region.
data ==
nullptr || region.
bytes == 0)
return;
129 if (stride == 0) stride = 4096;
130 auto* bytes =
static_cast<volatile std::uint8_t*
>(region.
data);
131 for (std::size_t offset = 0; offset < region.
bytes; offset += stride) bytes[offset] = 0;
132 bytes[region.
bytes - 1] = 0;
136#if defined(__linux__)
137 if (region.data !=
nullptr && region.bytes != 0) ::munmap(region.data, region.bytes);
139 region.data =
nullptr;
141 region.native_policy_applied =
false;
147 out += placement.
node_id ?
" node=" + std::to_string(*placement.
node_id) :
" node=none";
148 out += placement.
cpu_id ?
" cpu=" + std::to_string(*placement.
cpu_id) :
" cpu=none";
NumaPlacement choose_numa_placement(const NumaInfo &info, NumaMode mode, std::size_t worker_index)
NumaMemoryRegion allocate_numa_memory(std::size_t bytes, const NumaInfo &info, NumaMode mode, std::optional< std::size_t > node_id=std::nullopt) noexcept
std::vector< NumaPlacement > plan_numa_workers(const NumaInfo &info, NumaMode mode, std::size_t workers)
bool pin_current_thread_to_cpu(std::size_t cpu_id) noexcept
std::string describe_numa_placement(const NumaPlacement &placement)
void release_numa_memory(NumaMemoryRegion ®ion) noexcept
bool linux_mbind_region(void *address, std::size_t bytes, NumaMode mode, std::optional< std::size_t > node_id, std::size_t max_node_id) noexcept
void first_touch_region(NumaMemoryRegion ®ion, std::size_t stride=4096) noexcept
bool apply_numa_placement(const NumaPlacement &placement) noexcept
std::string numa_mode_name(NumaMode mode)
std::vector< NumaNodeInfo > topology
std::optional< std::size_t > node_id
bool native_policy_applied
std::optional< std::size_t > cpu_id
std::optional< std::size_t > node_id