NUMA++ 0.12.0
Loading...
Searching...
No Matches
memory.cpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup numapp_mem
4 * @brief Definition of memory functions from <numapp/memory.hpp>
5 * @copyright
6 * SPDX-FileCopyrightText: 2021-2025 European Southern Observatory (ESO)
7 *
8 * SPDX-License-Identifier: LGPL-3.0-only
9 */
10#include <numapp/memory.hpp>
11
12#include <bit>
13#include <cassert>
14#include <numa.h>
15#include <type_traits>
16#include <utility>
17#include <x86intrin.h>
18
19#include <numapp/mempolicy.hpp>
20
21namespace numapp {
22namespace {
23/**
24 * Rounds allocation size up to nearest page size. `page_size` must be a power of two.
25 */
26constexpr auto AlignedCeil(std::size_t size, std::size_t page_size) noexcept -> std::size_t {
27 if (size == 0) {
28 std::unreachable();
29 }
30 if (page_size == 0) {
31 std::unreachable();
32 }
33 if ((page_size & (page_size - 1)) != 0) {
34 // Page size is power of 2
35 std::unreachable();
36 }
37
38 return ((size + page_size - 1) / page_size) * page_size;
39}
40} // namespace
41
42std::size_t GetPageSize() noexcept {
43 return numa_pagesize();
44}
45
46int GetNumNodes() noexcept {
47 return numa_num_configured_nodes();
48}
49
50std::optional<int> GetNodeDistance(int node1, int node2) noexcept {
51 int distance = numa_distance(node1, node2);
52 return distance == 0 ? std::nullopt : std::optional<int>{distance};
53}
54
55std::optional<int> GetNodeOfCpu(int cpu) noexcept {
56 int node = numa_node_of_cpu(cpu);
57 return node == -1 ? std::nullopt : std::optional<int>{node};
58}
59
60std::error_code MemLock(void const* addr, std::size_t len, LockFlag flag) noexcept {
61 if (mlock2(addr, len, static_cast<std::underlying_type<LockFlag>::type>(flag)) != 0) {
62 return std::make_error_code(static_cast<std::errc>(errno));
63 }
64 return {};
65}
66
67std::error_code MemUnlock(void const* addr, std::size_t len) noexcept {
68 if (munlock(addr, len) != 0) {
69 return std::make_error_code(static_cast<std::errc>(errno));
70 }
71 return {};
72}
73
74std::error_code MemLockAll(LockAllFlag flags) noexcept {
75 if (mlockall(static_cast<std::underlying_type<LockAllFlag>::type>(flags)) != 0) {
76 return std::make_error_code(static_cast<std::errc>(errno));
77 }
78 return {};
79}
80
81std::error_code MemUnlockAll() noexcept {
82 if (munlockall() != 0) {
83 return std::make_error_code(static_cast<std::errc>(errno));
84 }
85 return {};
86}
87
88[[nodiscard]] void*
89Allocate(std::size_t size, MemPolicy const& policy, std::error_code& ec) noexcept {
90 return Allocate(size, policy, MemPolicyFlag::None, ec);
91}
92
93[[nodiscard]] void* Allocate(std::size_t size,
94 MemPolicy const& policy,
95 MemPolicyFlag flags,
96 std::error_code& ec) noexcept {
97 auto ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
98 if (ptr == MAP_FAILED) { // NOLINT
99 ec = std::make_error_code(static_cast<std::errc>(errno));
100 return nullptr;
101 }
102
103 ec = Apply(ptr, size, policy, flags);
104 if (ec) {
105 (void)munmap(ptr, size);
106 return nullptr;
107 }
108 return ptr;
109}
110
111[[nodiscard]] void* Allocate(std::size_t size, MemPolicy const& policy) {
112 std::error_code ec;
113 auto ptr = Allocate(size, policy, ec);
114 if (ec) {
115 assert(ptr == nullptr);
116 throw std::system_error(ec);
117 }
118 return ptr;
119}
120
121[[nodiscard]] void* Allocate(std::size_t size, MemPolicy const& policy, MemPolicyFlag flags) {
122 std::error_code ec;
123 auto ptr = Allocate(size, policy, flags, ec);
124 if (ec) {
125 assert(ptr == nullptr);
126 throw std::system_error(ec);
127 }
128 return ptr;
129}
130
131void Free(void* ptr, std::size_t size, std::error_code& ec) noexcept {
132 if (munmap(ptr, size) == -1) {
133 ec = std::make_error_code(static_cast<std::errc>(errno));
134 } else {
135 ec.clear();
136 }
137}
138
139void Free(void* ptr, std::size_t size) {
140 std::error_code ec;
141 Free(ptr, size, ec);
142 if (ec) {
143 throw std::system_error(ec);
144 }
145}
146
148 : m_size(static_cast<std::size_t>(preset)) {
149}
150
151HugePageSize::HugePageSize(std::size_t bytes) : m_size(bytes) {
152 // Maintain invariant that `bytes` is an integer power of two and that base-2 log is not 0,
153 // other than that you can technically request surprising page sizes with `mmap`.
154 if (m_size < 2u) {
155 throw std::invalid_argument("Invalid page size");
156 }
157 if (!std::has_single_bit(m_size)) {
158 throw std::invalid_argument("Invalid page size");
159 }
160}
161
162auto EncodeMmapFlags(HugePageSize page_size) noexcept -> int {
163 // HugePageSize maintain the invariant that the size is a round base-2 log we can simplify to:
164 return std::countr_zero(page_size.Size()) << MAP_HUGE_SHIFT;
165}
166
167void* AllocateHuge(std::size_t size,
168 HugePageSize page_size,
169 MemPolicy const& policy,
170 MemPolicyFlag flags,
171 std::error_code& ec) noexcept {
172 auto const tlb_bits = EncodeMmapFlags(page_size);
173 auto alloc_size = AlignedCeil(size, page_size.Size());
174 // note: Although freeing huge pages with `munmap()` requires length to be multiple of page
175 // size, `mmap()` does not.
176 auto ptr = mmap(nullptr,
177 alloc_size,
178 PROT_READ | PROT_WRITE,
179 MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | tlb_bits,
180 0,
181 0);
182 if (ptr == MAP_FAILED) { // NOLINT
183 ec = std::make_error_code(static_cast<std::errc>(errno));
184 return nullptr;
185 }
186
187 ec = Apply(ptr, alloc_size, policy, flags);
188 if (ec) {
189 (void)munmap(ptr, alloc_size);
190 return nullptr;
191 }
192 return ptr;
193}
194
195void* AllocateHuge(std::size_t size,
196 HugePageSize page_size,
197 MemPolicy const& policy,
198 MemPolicyFlag flags) {
199 std::error_code ec;
200 auto* ptr = AllocateHuge(size, page_size, policy, flags, ec);
201 if (ec) {
202 throw std::system_error(ec, "AllocateHuge failed");
203 }
204 return ptr;
205}
206
207void FreeHuge(void* ptr, std::size_t size, HugePageSize page_size, std::error_code& ec) noexcept {
208 // note: `munmap()` documents that for huge pages the size must be a multiple of the page size.
209 auto const alloc_size = AlignedCeil(size, page_size.Size());
210 if (munmap(ptr, alloc_size) == -1) {
211 ec = std::make_error_code(static_cast<std::errc>(errno));
212 } else {
213 ec.clear();
214 }
215}
216
217void FreeHuge(void* ptr, std::size_t size, HugePageSize page_size) {
218 std::error_code ec;
219 FreeHuge(ptr, size, page_size, ec);
220 if (ec) {
221 throw std::system_error(ec, "FreeHuge failed");
222 }
223}
224
225PageResource::PageResource(MemPolicy policy, MemPolicyFlag flags)
226 : m_policy(std::move(policy)), m_flags(flags) {
227}
228
229auto PageResource::GetPolicy() const noexcept -> MemPolicy const& {
230 return m_policy;
231}
232
233auto PageResource::GetFlags() const noexcept -> MemPolicyFlag {
234 return m_flags;
235}
236
237void* PageResource::do_allocate(std::size_t bytes, std::size_t alignment) {
238 return Allocate(bytes, m_policy, m_flags);
239}
240
241void PageResource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment) {
242 return Free(p, bytes);
243}
244
245bool PageResource::do_is_equal(const std::pmr::memory_resource& other) const noexcept {
246 if (auto const* o = dynamic_cast<PageResource const*>(&other); o != nullptr) {
247 return GetFlags() == o->GetFlags() && GetPolicy() == o->GetPolicy();
248 }
249 return false;
250}
251
252HugePageResource::HugePageResource(HugePageSize page_size, MemPolicy policy, MemPolicyFlag flags)
253 : m_page_size(page_size), m_policy(std::move(policy)), m_flags(flags) {
254}
255
256auto HugePageResource::GetPolicy() const noexcept -> MemPolicy const& {
257 return m_policy;
258}
259
260auto HugePageResource::GetFlags() const noexcept -> MemPolicyFlag {
261 return m_flags;
262}
263
265 return m_page_size;
266}
267
268void* HugePageResource::do_allocate(std::size_t bytes, std::size_t alignment) {
269 return AllocateHuge(bytes, m_page_size, m_policy, m_flags);
270}
271
272void HugePageResource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment) {
273 return FreeHuge(p, bytes, m_page_size);
274}
275
276bool HugePageResource::do_is_equal(const std::pmr::memory_resource& other) const noexcept {
277 if (auto const* o = dynamic_cast<HugePageResource const*>(&other); o != nullptr) {
278 return GetFlags() == o->GetFlags() && GetPolicy() == o->GetPolicy() &&
279 GetPageSize() == o->GetPageSize();
280 }
281 return false;
282}
283
285 : m_upstream(std::pmr::get_default_resource()), m_flag(flag) {
286}
287
288LockResource::LockResource(std::pmr::memory_resource* upstream) noexcept
289 : m_upstream(upstream), m_flag(LockFlag::PreFault) {
290 assert(upstream != nullptr);
291}
292
293LockResource::LockResource(LockFlag flag, std::pmr::memory_resource* upstream) noexcept
294 : m_upstream(upstream), m_flag(flag) {
295 assert(upstream != nullptr);
296}
297
298auto LockResource::do_allocate(std::size_t bytes, std::size_t alignment) -> void* {
299 auto* mem = m_upstream->allocate(bytes, alignment);
300 assert(mem != nullptr);
301 auto ec = MemLock(mem, bytes, m_flag);
302 if (ec) {
303 m_upstream->deallocate(mem, bytes, alignment);
304 throw std::system_error(ec, "MemLock failed");
305 }
306 return mem;
307}
308
309void LockResource::do_deallocate(void* p, std::size_t bytes, std::size_t alignment) {
310 m_upstream->deallocate(p, bytes, alignment);
311}
312
313auto LockResource::do_is_equal(const std::pmr::memory_resource& other) const noexcept -> bool {
314 if (auto const* o = dynamic_cast<LockResource const*>(&other); o != nullptr) {
315 return GetFlags() == o->GetFlags() && GetUpstream() == o->GetUpstream();
316 }
317 return false;
318}
319
320auto LockResource::GetFlags() const noexcept -> LockFlag {
321 return m_flag;
322}
323
324auto LockResource::GetUpstream() const noexcept -> std::pmr::memory_resource* {
325 return m_upstream;
326}
327
328} // namespace numapp
Polymorphic memory resource allocating huge pages with specified NUMA policy.
Definition memory.hpp:682
auto GetPageSize() const noexcept -> HugePageSize
Get page size.
Definition memory.cpp:264
HugePageResource(HugePageSize page_size, MemPolicy policy, MemPolicyFlag flags=MemPolicyFlag::None)
Create PageResource with specified memory policy and flags.
Definition memory.cpp:252
auto GetFlags() const noexcept -> MemPolicyFlag
Get flags in use.
Definition memory.cpp:260
auto GetPolicy() const noexcept -> MemPolicy const &
Get memory policy in use.
Definition memory.cpp:256
Describes a huge page size and maintains the invariant that page size is an integral power of 2,...
Definition memory.hpp:536
HugePageSize(HugePagePreset preset) noexcept
Construct from preset page size value.
Definition memory.cpp:147
Lock memory allocated from upstream memory resource using specified LockFlag.
Definition memory.hpp:408
LockResource(LockFlag flag) noexcept
Initialize with specified flag and upstream memory resource initialized from std::pmr::get_default_re...
Definition memory.cpp:284
auto GetUpstream() const noexcept -> std::pmr::memory_resource *
Get upstream memory resource.
Definition memory.cpp:324
auto GetFlags() const noexcept -> LockFlag
Get lock flag in use.
Definition memory.cpp:320
Class representing a memory policy that can be modified and used to apply to the current thread or a ...
MemPolicy(Mode mode, Nodemask &&node_mask) noexcept
Create memory policy.
Polymorphic memory resource allocating full system pages with specified NUMA policy.
Definition memory.hpp:361
auto GetFlags() const noexcept -> MemPolicyFlag
Get flags in use.
Definition memory.cpp:233
PageResource(MemPolicy policy, MemPolicyFlag flags=MemPolicyFlag::None)
Create PageResource with specified memory policy and flags.
Definition memory.cpp:225
auto GetPolicy() const noexcept -> MemPolicy const &
Get memory policy in use.
Definition memory.cpp:229
std::error_code Apply(pid_t thread, CpuAffinity const &affinity) noexcept
Apply policy to specified thread.
void FreeHuge(void *ptr, std::size_t size, HugePageSize page_size, std::error_code &ec) noexcept
Free huge pages previously allocated with AllocateHuge.
Definition memory.cpp:207
auto EncodeMmapFlags(HugePageSize page_size) noexcept -> int
Encodes page size into bit representation expected by mmap().
Definition memory.cpp:162
HugePagePreset
Preset huge page sizes.
Definition memory.hpp:478
void * AllocateHuge(std::size_t size, HugePageSize page_size, MemPolicy const &policy, MemPolicyFlag flags, std::error_code &ec) noexcept
Non-throwing version of AllocateHuge()
Definition memory.cpp:167
int GetNumNodes() noexcept
Query number of configured NUMA nodes.
Definition memory.cpp:46
std::error_code MemLock(void const *addr, std::size_t len, LockFlag flag) noexcept
Lock memory pages in the specified address range.
Definition memory.cpp:60
std::size_t GetPageSize() noexcept
Fast query of system page size.
Definition memory.cpp:42
LockAllFlag
Flags that are combined to modify behaviour of MemLockAll().
Definition memory.hpp:148
std::error_code MemUnlock(void const *addr, std::size_t len) noexcept
Unlock memory pages in the specified address range.
Definition memory.cpp:67
std::optional< int > GetNodeDistance(int node1, int node2) noexcept
Get NUMA distance between two nodes.
Definition memory.cpp:50
MemPolicyFlag
Flag that modify the behaviour of applying a memory policy to a range of memory.
Definition mempolicy.hpp:38
void Free(void *ptr, std::size_t size, std::error_code &ec) noexcept
See group for details.
Definition memory.cpp:131
LockFlag
Mutually exclusive flags that modifies behaviour of MemLock().
Definition memory.hpp:121
void * Allocate(std::size_t size, MemPolicy const &policy, std::error_code &ec) noexcept
See group for details.
Definition memory.cpp:89
std::error_code MemUnlockAll() noexcept
Unlock all locked memory in this process.
Definition memory.cpp:81
std::error_code MemLockAll(LockAllFlag flags) noexcept
Lock all memory pages as specified by provided flags.
Definition memory.cpp:74
std::optional< int > GetNodeOfCpu(int cpu) noexcept
Get NUMA node of the given CPU.
Definition memory.cpp:55
@ PreFault
Locks pages whether they are resident or not.
Definition memory.hpp:128
Contains memory function declarations.
Contains declarations for numapp::MemPolicy.