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