NUMA++ 0.11.0
Loading...
Searching...
No Matches
mempolicy.hpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup numapp_mem
4 * @copyright ESO 2024 - European Southern Observatory
5 *
6 * @brief Contains declarations for numapp::MemPolicy.
7 *
8 * @defgroup numapp_mem Memory APIs
9 * @ingroup numapp
10 * @brief NUMA++ memory policy APIs
11 *
12 * See @ref memory-api-policy for an overview.
13 */
14#ifndef NUMAPP_MEMPOLICY_HPP_
15#define NUMAPP_MEMPOLICY_HPP_
16#include <numapp/config.hpp>
17
18#include <iosfwd>
19#include <memory>
20#include <numaif.h>
21#include <optional>
22#include <system_error>
23
24#include <numapp/flags.hpp>
25#include <numapp/nodemask.hpp>
26
27namespace numapp {
28class MemPolicy;
29
30/**
31 * Flag that modify the behaviour of applying a memory policy to a range of memory.
32 * @ingroup numapp_mem
33 * @headerfile <> <numapp/mempolicy.hpp>
34 * @relatesalso MemPolicy
35 */
36enum class MemPolicyFlag : unsigned {
37 /**
38 * No-op flag.
39 */
40 None = 0u,
41 /**
42 * Determines if application of memory policy should be strict and fail if MemPolicy cannot be
43 * used rather than e.g. allocate from other nodes.
44 *
45 * Cause Apply(void*, std::size_t, MemPolicy, MemPolicyFlag) to fail if existing pages don't
46 * follow the policy. If combined with Move the operation will fail if all pages couldn't be
47 * moved.
48 *
49 * This does not apply to MemPolicy::Mode::Default or MemPolicy::Mode::Interleaved.
50 */
51 Strict = MPOL_MF_STRICT,
52 /**
53 * Attempt to move pages so that they follow the policy. Can be combined with Strict to cause
54 * operation to fail if pages couldn't be moved.
55 */
56 Move = MPOL_MF_MOVE,
57 /**
58 * Attempt to move pages so that they follow the policy regardless whether another process is
59 * using the pages. This requires capability CAP_SYS_NICE.
60 *
61 * Can be combined with Strict to have the operation fail if all pages couldn't be moved.
62 */
63 MoveAll = MPOL_MF_MOVE_ALL,
64};
65
66NUMAPP_ENABLE_BITFLAG(MemPolicyFlag)
67
68namespace thisThread {
69
70/**
71 * @name Apply Memory Policy to Current Thread
72 *
73 * Applies default memory policy to calling thread or to thread stack memory.
74 * @ingroup numapp_mem
75 */
76/// @{
77
78/**
79 * Applies the default memory policy to the calling thread.
80 *
81 * @note The memory policy is only applied to new physical page allocations.
82 *
83 * @param policy memory policy to set.
84 *
85 * @manpages
86 * @manpage{set_mempolicy,2}
87 *
88 * @relatesalso numapp::MemPolicy
89 * @ingroup numapp_mem
90 */
91[[nodiscard]] std::error_code Apply(MemPolicy const& policy) noexcept;
92
93/**
94 * Convenience function that applies a memory policy to current thread stack memory.
95 *
96 * @note It relies on pthread API get the stack memory address and size. No pre-faulting is made.
97 *
98 * @param policy Memory policy to apply.
99 * @param flags combination of MemPolicyFlag values that modify the behaviour. Default is to move
100 * pages and fail if this cannot be done.
101 *
102 * @manpages
103 * @manpage{mbind,2}
104 * @relatesalso MemPolicy
105 * @ingroup numapp_mem
106 */
107[[nodiscard]] std::error_code
108ApplyStack(MemPolicy const& policy,
109 MemPolicyFlag flags = MemPolicyFlag::Move | MemPolicyFlag::Strict) noexcept;
110/// @}
111
112} // namespace thisThread
113
114/**
115 * @name Apply Memory Policy to Memory Range
116 */
117/// @{
118
119/**
120 * Applies memory policy to the memory pages that spans the range [@a address, @a adress +
121 * @a length].
122 *
123 * @note The memory policy is by default only applied to new physical page allocations. This can be
124 * modified using MemPolicyFlag::Move and MemPolicyFlag::MoveAll.
125 *
126 * Read related man-page of @c mbind() to understand the various behaviours if memory is mapped or
127 * shared.
128 *
129 * @param address start of address range.
130 * @param length length of range.
131 * @param policy Policy to apply.
132 * @param flags combination of MemPolicyFlag values that modify the behaviour.
133 * @returns 0 if successful.
134 * @returns non-zero if it failed.
135 *
136 * @manpages
137 * @manpage{mbind,2}
138 * @relatesalso MemPolicy
139 */
140[[nodiscard]] std::error_code Apply(void* address,
141 std::size_t length,
142 MemPolicy const& policy,
143 MemPolicyFlag flags) NUMAPP_NOEXCEPT;
144///@}
145
146/**
147 * Class representing a memory policy that can be modified and used to apply to the current thread
148 * or a memory range.
149 *
150 * Uses numa APIs @c set_mempolicy and @c get_mempolicy.
151 *
152 * To read the current memory policy use MemPolicy::MakeFromActive. To apply a policy use
153 * thisThread::Apply(MemPolicy const&).
154 *
155 * For CPU policy see @c numapp::CpuAffinity.
156 *
157 * @attention The memory policy is used for new physical page allocations. One way this happens is
158 * by writing to unpaged memory which triggers a page-fault that then is handled according to the
159 * policy. This means that it's possible that memory returned by an allocator like
160 * @c malloc/@c std::allocator/@c new after a sucessfull call to MemPolicy::Apply does not in
161 * fact allocate a new physical page but may reuse recently freed memory or otherwise unused, but
162 * physically allocated memory.
163 *
164 * @attention Bottom line is that MemPolicy does @a not apply directly to virtual memory allocators
165 * but on the kernel level page allocation.
166 *
167 * @headerfile <> <numapp/mempolicy.hpp>
168 * @related numapp::ScopedMemPolicy
169 * @manpages
170 * @manpage{get_mempolicy,2}
171 * @manpage{set_mempolicy,2}
172 * @ingroup numapp_mem
173 */
175public:
176 /**
177 * Memory allocation modes.
178 */
179 enum class Mode : int {
180 /**
181 * The Default mode specifies that any nondefault process memory policy be removed, so that
182 * the memory policy "falls back" to the system default policy. The system default policy is
183 * "local allocation"-- i.e., allocate memory on the node of the CPU that triggered the
184 * allocation. nodemask must be specified as NULL. If the "local node" contains no free
185 * memory, the system will attempt to allocate memory from a "near by" node.
186 *
187 * Corresponds to native mode @c MPOL_DEFAULT.
188 */
189 Default = MPOL_DEFAULT,
190 /**
191 * The Bind mode defines a strict policy that restricts memory allocation to the nodes
192 * specified in nodemask. If nodemask specifies more than one node, page allocations will
193 * come from the node with the lowest numeric node ID first, until that node contains no
194 * free memory. Allocations will then come from the node with the next highest node ID
195 * specified in nodemask and so forth, until none of the specified nodes contain free
196 * memory. Pages will not be allocated from any node not specified in the nodemask.
197 *
198 * Corresponds to native mode @c MPOL_BIND.
199 */
200 Bind = MPOL_BIND,
201 /**
202 * Interleave interleaves page allocations across the nodes specified in nodemask in
203 * numeric node ID order. This optimizes for bandwidth instead of latency by spreading out
204 * pages and memory accesses to those pages across multiple nodes. However, accesses to a
205 * single page will still be limited to the memory bandwidth of a single node.
206 *
207 * Corresponds to native mode @c MPOL_INTERLEAVE.
208 */
209 Interleave = MPOL_INTERLEAVE,
210 /**
211 * Preferred sets the preferred node for allocation. The kernel will try to allocate pages
212 * from this node first and fall back to "near by" nodes if the preferred node is low on
213 * free memory. If nodemask specifies more than one node ID, the first node in the mask will
214 * be selected as the preferred node. If the nodemask and maxnode arguments specify the
215 * empty set, then the policy specifies "local allocation" (like the system default policy
216 * discussed above).
217 *
218 * Corresponds to native mode @c MPOL_PREFERRED.
219 */
220 Preferred = MPOL_PREFERRED
221 };
222
223 /**
224 * Create memory policy.
225 *
226 * @param mode Policy mode
227 * @param node_mask
228 */
229 explicit MemPolicy(Mode mode, Nodemask&& node_mask) noexcept
230 : m_mode(mode), m_nodes(std::move(node_mask)) {
231 }
232 explicit MemPolicy(Mode mode, Nodemask const& node_mask) : m_mode(mode), m_nodes(node_mask) {
233 }
234
235 MemPolicy(MemPolicy&&) noexcept = default;
236 MemPolicy& operator=(MemPolicy&&) noexcept = default;
237 MemPolicy(MemPolicy const& rhs) = default;
238 MemPolicy& operator=(MemPolicy const& rhs) noexcept = default;
239
240 /**
241 * Creates a strict policy (using @c MPOL_BIND) to allocate all memory to the specified node.
242 *
243 * @param node Numa node.
244 * @throws std::system_error if it fails.
245 */
246 [[nodiscard]] static MemPolicy MakeBindNode(int node);
247
248 /**
249 * Create MemPolicy from mode and `nodestring` while considering currently allowed nodes.
250 *
251 *
252 * Nodestring use patterns such as:
253 *
254 * - <tt>1-5,7,10</tt>
255 * - <tt>!4-5</tt>
256 * - <tt>+0-3</tt>
257 * - <tt>all</tt>
258 *
259 * @manpages
260 * @manpage{numa,3}
261 *
262 * @param mode Memory policy mode such as @c MPOL_BIND
263 * @param nodestring Nodes to include such as <tt>1-5,7,10</tt> or <tt>all</tt>.
264 * @throws std::system_error on failure. This can also happen if the @a nodestring references a
265 * disallowed node. Use @ref MakeFromNodeStringAll to ignore this.
266 *
267 * @sa MakeFromNodeStringAll
268 */
269 [[nodiscard]] static MemPolicy MakeFromNodeString(Mode mode, char const* nodestring) {
270 return MemPolicy(mode, Nodemask::MakeFromNodestring(nodestring));
271 }
272
273 /**
274 * Create MemPolicy from mode and `nodestring` considering all nodes, whether they are allowed
275 * or not.
276 *
277 * Nodestring use patterns such as:
278 *
279 * - <tt>1-5,7,10</tt>
280 * - <tt>!4-5</tt>
281 * - <tt>+0-3</tt>
282 * - <tt>all</tt>
283 *
284 * @manpages
285 * @manpage{numa,3}
286 *
287 * @param mode Memory policy mode such as @c MPOL_BIND
288 * @param nodestring Nodes to include such as <tt>1-5,7,10</tt> or <tt>all</tt>.
289 * @throws std::system_error on failure. This can also happen if the @a nodestring references a
290 * disallowed node. Use @ref MakeFromNodeStringAll to ignore this.
291 *
292 * @sa MakeFromNodeString
293 */
294 [[nodiscard]] static MemPolicy MakeFromNodeStringAll(Mode mode, char const* nodestring) {
295 return MemPolicy(mode, Nodemask::MakeFromNodestringAll(nodestring));
296 }
297
298 /**
299 * Make from active default policy of calling thread.
300 *
301 * @throws std::system_error if it fails.
302 */
303 [[nodiscard]] static MemPolicy MakeFromActive();
304
305 /**
306 * Make from policy at address.
307 *
308 * @param address memory address to make policy from.
309 * @throws std::system_error if it fails.
310 */
311 [[nodiscard]] static MemPolicy MakeFromAddress(void* address);
312
313 /**
314 * @return true if this memory policy mode and nodes equals @a rhs.
315 */
316 [[nodiscard]] bool operator==(MemPolicy const& rhs) const noexcept {
317 return m_mode == rhs.m_mode && m_nodes == rhs.m_nodes;
318 }
319 [[nodiscard]] bool operator!=(MemPolicy const& rhs) const noexcept {
320 return !(*this == rhs);
321 }
322
323 /**
324 * @return policy mode.
325 */
326 [[nodiscard]] Mode GetMode() const noexcept {
327 return m_mode;
328 }
329
330 /**
331 * Set memory policy mode.
332 *
333 * @param mode policy mode.
334 */
335 void SetMode(Mode mode) noexcept {
336 m_mode = mode;
337 }
338
339 /**
340 * @return Node mask.
341 */
342 Nodemask const& GetNodemask() const noexcept {
343 return m_nodes;
344 }
345
346private:
347 MemPolicy() : m_mode(Mode::Default), m_nodes() {
348 }
349
350 Mode m_mode;
351 Nodemask m_nodes;
352};
353
354/**
355 * Formats @a mode and inserts it to @a os.
356 *
357 * @param os output stream to insert into.
358 * @param mode Memory policy mode to format.
359 * @returns @a os
360 *
361 * @relates MemPolicy
362 */
363std::ostream& operator<<(std::ostream& os, MemPolicy::Mode const& mode);
364
365/**
366 * Formats @a mempolicy and inserts it to @a os.
367 *
368 * @param os output stream to insert into.
369 * @param mempolicy Memory policy to format.
370 * @returns @a os
371 *
372 * @relates MemPolicy
373 */
374std::ostream& operator<<(std::ostream& os, MemPolicy const& mempolicy);
375
376/**
377 * Applies memory policy to this thread at construction and reverts to previous at destruction.
378 *
379 * @sa numapp::MemPolicy
380 *
381 * @ingroup numapp_mem
382 */
383class [[maybe_unused]] ScopedMemPolicy {
384public:
385 /**
386 * Constructs an object that applies the provided policy during construction
387 * and restores it during destruction.
388 *
389 * @throws std::system_error containing the error code if it fails to apply new policy.
390 * Attempt to restore old policy will be done.
391 */
392 explicit ScopedMemPolicy(MemPolicy const& new_policy)
393 : m_old_policy(MemPolicy::MakeFromActive()) {
394 auto ec = thisThread::Apply(new_policy);
395 if (ec) {
396 throw std::system_error(ec);
397 }
398 }
399
400 /**
401 * Restores old policy, if any.
402 */
403 ~ScopedMemPolicy() noexcept {
404 (void)Restore();
405 }
406
407 /**
408 * Initialized a scoped memory policy that does nothing
409 * and holds no state about previous policy.
410 */
411 ScopedMemPolicy() noexcept = default;
412
413 /**
414 * Swap state with another @c ScopedMemPolicy.
415 */
416 void Swap(ScopedMemPolicy& rhs) noexcept {
417 m_old_policy.swap(rhs.m_old_policy);
418 }
419
420 /**
421 * Restores memory policy that was read during construction.
422 *
423 * @returns the error that caused it to fail to apply old policy.
424 */
425 [[nodiscard]] std::error_code Restore() noexcept;
426
427private:
428 std::optional<MemPolicy> m_old_policy;
429};
430
431} // namespace numapp
432#endif // #ifndef NUMAPP_MEMPOLICY_HPP_
Class representing a memory policy that can be modified and used to apply to the current thread or a ...
std::ostream & operator<<(std::ostream &os, MemPolicy const &mempolicy)
Formats mempolicy and inserts it to os.
Mode
Memory allocation modes.
@ Bind
The Bind mode defines a strict policy that restricts memory allocation to the nodes specified in node...
@ Default
The Default mode specifies that any nondefault process memory policy be removed, so that the memory p...
@ Preferred
Preferred sets the preferred node for allocation.
@ Interleave
Interleave interleaves page allocations across the nodes specified in nodemask in numeric node ID ord...
MemPolicy(Mode mode, Nodemask &&node_mask) noexcept
Create memory policy.
Nodemask const & GetNodemask() const noexcept
bool operator==(MemPolicy const &rhs) const noexcept
static MemPolicy MakeFromActive()
Make from active default policy of calling thread.
static MemPolicy MakeFromNodeString(Mode mode, char const *nodestring)
Create MemPolicy from mode and nodestring while considering currently allowed nodes.
Mode GetMode() const noexcept
void SetMode(Mode mode) noexcept
Set memory policy mode.
static MemPolicy MakeFromAddress(void *address)
Make from policy at address.
static MemPolicy MakeBindNode(int node)
Creates a strict policy (using MPOL_BIND) to allocate all memory to the specified node.
std::ostream & operator<<(std::ostream &os, MemPolicy::Mode const &mode)
Formats mode and inserts it to os.
static MemPolicy MakeFromNodeStringAll(Mode mode, char const *nodestring)
Create MemPolicy from mode and nodestring considering all nodes, whether they are allowed or not.
std::error_code Apply(void *address, std::size_t length, MemPolicy const &policy, MemPolicyFlag flags) noexcept
Applies memory policy to the memory pages that spans the range [address, adress + length].
ScopedMemPolicy() noexcept=default
Initialized a scoped memory policy that does nothing and holds no state about previous policy.
void Swap(ScopedMemPolicy &rhs) noexcept
Swap state with another ScopedMemPolicy.
ScopedMemPolicy(MemPolicy const &new_policy)
Constructs an object that applies the provided policy during construction and restores it during dest...
std::error_code Restore() noexcept
Restores memory policy that was read during construction.
~ScopedMemPolicy() noexcept
Restores old policy, if any.
NUMA++ configuration.
Contains definitions for bitwise operators for enums.
#define NUMAPP_ENABLE_BITFLAG(enum)
Enables numapp bitwise operators in overload set for the specified enumeration enum.
Definition flags.hpp:17
std::error_code Apply(CpuAffinity const &affinity) noexcept
Apply policy to calling thread.
MemPolicyFlag
Flag that modify the behaviour of applying a memory policy to a range of memory.
Definition mempolicy.hpp:36
std::error_code ApplyStack(MemPolicy const &policy, MemPolicyFlag flags=MemPolicyFlag::Move|MemPolicyFlag::Strict) noexcept
Convenience function that applies a memory policy to current thread stack memory.
std::error_code Apply(MemPolicy const &policy) noexcept
Applies the default memory policy to the calling thread.
Contains declarations for Nodemask.
Type-safe NUMA node mask.
Definition nodemask.hpp:23
static Nodemask MakeFromNodestringAll(char const *nodestring)
Construct Nodemask from nodestring that considers does not consider current cpuset.
Definition nodemask.hpp:56
static Nodemask MakeFromNodestring(char const *nodestring)
Construct Nodemask from nodestring that considers current cpuset.
Definition nodemask.hpp:41