CPU亲和性及NUMA架构¶
isolcpus:内核启动参数CPU隔离
编辑 /etc/default/grub 。
修改 GRUB_CMDLINE_LINUX_DEFAULT 这一行, 在引号内添加 isolcpus=cpu号列表 (例如: isolcpus=2,3 isolcpus=1,4-7)。
执行 sudo update-grub (Debian/Ubuntu) ( sudo grub2-mkconfig -o /boot/grub2/grub.cfg(RHEL/CentOS/Fedora)) 并 sudo reboot。
taskset:命令行工具CPU绑定
启动进程: taskset -c 1 ./my_app (在CPU 1上运行)
修改运行中进程:taskset -pc 3 <PID> (将PID进程移至CPU 3)
查询进程:taskset -pc <PID>
绑定进程下的线程: ps -T -p <PID> taskset -p -c <CPU列表> <TID>
pthread_setaffinity_np(sched_setaffinity):线程(进程)CPU绑定
#pragma once
#include <sys/syscall.h>
#include <unistd.h>
#include <atomic>
#include <iostream>
#include <thread>
namespace Common {
/// Set affinity for current thread to be pinned to the provided core_id.
inline auto setThreadCore(int core_id) noexcept {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core_id, &cpuset);
return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) ==
0);
}
/// Creates a thread instance, sets affinity on it, assigns it a name and
/// passes the function to be run on that thread as well as the arguments to the
/// function.
template <typename T, typename... A>
inline auto createAndStartThread(int core_id, const std::string& name, T&& func,
A&&... args) noexcept {
auto t = new std::thread([&]() {
if (core_id >= 0 && !setThreadCore(core_id)) {
std::cerr << "Failed to set core affinity for " << name << " "
<< pthread_self() << " to " << core_id << std::endl;
exit(EXIT_FAILURE);
}
std::cerr << "Set core affinity for " << name << " " << pthread_self()
<< " to " << core_id << std::endl;
std::forward<T>(func)((std::forward<A>(args))...);
});
using namespace std::literals::chrono_literals;
std::this_thread::sleep_for(1s);
return t;
}
} // namespace Common
/*
* sched_setaffinity 和 pthread_setaffinity_np 的区别:
*
* 1. 作用对象:
* - pthread_setaffinity_np: 作用线程,通过 pthread_t 句柄来指定要绑定的线程。
* - sched_setaffinity: 作用进程。它通过进程ID (PID) 来指定要绑定的进程。
* 一个进程被绑定,所有子线程也会被限制在这个CPU中。
* 2. 可移植性:
* - pthread_setaffinity_np: GNU C 库(glibc)扩展。
* - sched_setaffinity: 标准 Linux 系统调用。
*/
cpusets:CPU资源池隔离
#!/usr/bin/env bash
#
# simple_cpuset_example.sh
#
# 演示如何使用 cpuset 将一个任务绑定到专属的 CPU 核心。
# --- 配置 ---
# 为后台任务分配的常规核心
SYSTEM_CORE="0"
# 为专属任务保留的核心
EXCLUSIVE_CORE="1"
set -e
# 确保脚本以 root 权限运行
if [[ $(id -u) -ne 0 ]]; then
echo "此脚本必须以 root 身份运行。"
exit 1
fi
# 1. 创建 cgroup 目录
echo "--> 正在创建 cpuset 核心池..."
mkdir -p /sys/fs/cgroup/cpuset/exclusive_tasks
# 2. 配置独占核心池
# 将核心 1 分配给这个池
echo "${EXCLUSIVE_CORE}" > /sys/fs/cgroup/cpuset/exclusive_tasks/cpuset.cpus
# 将其标记为 CPU 独占
echo "1" > /sys/fs/cgroup/cpuset/exclusive_tasks/cpuset.cpu_exclusive
# 将内存节点 0 分配给这个池(通常一个核心只有一个内存节点)
echo "0" > /sys/fs/cgroup/cpuset/exclusive_tasks/cpuset.mems
# 3. 运行一个任务并将其绑定到独占核心池
echo "--> 正在启动一个无限循环并绑定到核心 ${EXCLUSIVE_CORE}..."
# 启动一个后台任务
while true; do :; done &
TASK_PID=$!
echo "任务 PID: ${TASK_PID}"
# 将任务的 PID 写入独占池的 tasks 文件中
echo "${TASK_PID}" > /sys/fs/cgroup/cpuset/exclusive_tasks/tasks
echo "任务已成功绑定。可以使用 'top' 或 'htop' 检查 PID ${TASK_PID} 是否正在核心 ${EXCLUSIVE_CORE} 上运行。"
echo "要停止任务,请运行:kill ${TASK_PID}"
| 特性 | isolcpus | cpusets | taskset | pthread_setaffinity_np |
|---|---|---|---|---|
| 层级 | 内核级 | 内核级 | 进程级 | 线程级 |
| 方式 | 启动参数 | 虚拟文件系统接口 | 命令行 | 库函数 |
| 隔离强度 | 强 | 强 | 弱 | 弱 |
| 粒度 | CPU | CPU & 内存节点池 | 进程 | 线程 |
| 动态性 | 静态 (需重启) | 动态 | 动态 | 动态 |
CPU的超线程技术(HT)将物理核模拟为两个逻辑核,共享核心的执行单元(如ALU、FPU)和L1/L2缓存。
(1)若绑定到同一物理核上的线程执行计算密集型操作,尤其是AVX等宽指令集,会完全占用共享的浮点单元,导致交易线程阻塞;
(2)线程对L1/L2缓存的访问会污染或驱逐交易线程的热点数据,增加缓存未命中。
(3)禁用超线程可使关键交易线程的执行延迟更低,降低延迟波动。
(4)禁用超线程会降低系统的整体吞吐量。因此将低延迟敏感任务独占绑定到物理核心(逻辑核ID的偶数或奇数部分),而将非实时性任务部署在启用超线程的核心上。(Linux系统层面实现)
lscpu -e # 假设输出显示 CPU 0 和 CPU 8 都属于 CORE 0。这意味着它们是同一个物理核心上的两个“兄弟”逻辑核心。
# 查看 CPU 8 的在线状态 (1 代表在线)
cat /sys/devices/system/cpu/cpu8/online
# 将 CPU 8 设置为离线 (禁用)
echo 0 > /sys/devices/system/cpu/cpu8/online
# 查看在线的CPU数量,会比原来少1
nproc
# 或者再次运行 lscpu,会看到 CPU 8 显示为 no (离线)
lscpu -e | grep "cpu8"
# 此时,操作系统调度器不会再向 CPU 8 分配任何任务。CPU 0 现在可以不受干扰地使用物理核心 0 的全部资源。
#重启只需
echo 1 > /sys/devices/system/cpu/cpu8/online
# 清晰地列出CPU、核心、Socket的对应关系
lscpu -e=CPU,CORE,SOCKET
# 绑定策略进程到物理核8-15(跳过超线程核)
taskset -c 8-15 ./strategy_engine
关闭 CPU 节能(C-States & P-States)
BIOS/UEFI 层面
| BIOS 设置项 | 推荐值 | 说明 |
|---|---|---|
| CPU C-States / Global C-State Control | Disabled |
禁用所有 C-State(C1~C10),CPU 永不进入休眠 |
| C1E Support | Disabled |
禁用增强型 C1 状态(即使 C-States 关闭,C1E 仍可能启用) |
| Intel SpeedStep (EIST) / AMD Cool'n'Quiet | Disabled |
禁用 P-State 动态调频,锁定频率 |
| Intel Turbo Boost / AMD Core Performance Boost | Disabled |
禁用自动超频,避免频率波动 |
| CPU Ratio / Multiplier | 固定值(如 45 ) |
手动设置 CPU 倍频,实现频率锁定(需同步禁用 SpeedStep) |
| Hyper-Threading / SMT Mode | Disabled |
可选:关闭超线程,减少调度干扰,便于精准控制。全局开关,会使精准控制超线程核策略失效 |
| Power Technology (AMD) | Custom → 手动关闭各项节能 |
AMD 平台注意项 |
Linux 内核参数
- 编辑 GRUB 配置
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash \
intel_idle.max_cstate=0 \
processor.max_cstate=1 \
idle=poll \
intel_pstate=disable"
| 参数 | 作用 |
|---|---|
intel_idle.max_cstate=0 |
强制 Intel CPU 的 intel_idle 驱动禁用所有 C-State(C1 及以上) |
processor.max_cstate=1 |
兼容备用驱动(acpi-cpufreq),限制最大 C-State 为 C1 |
idle=poll |
最激进设置:空闲时 CPU 不等待中断,而是持续轮询任务队列,实现最低延迟(但功耗极高) |
intel_pstate=disable |
禁用现代 Intel P-State 驱动,回退到传统 acpi-cpufreq ,便于手动控制频率 |
- 更新 GRUB 配置
- 重启生效
Linux 运行时控制
- 安装频率控制工具
# Ubuntu/Debian
sudo apt install cpufrequtils linux-tools-common
# CentOS/RHEL
sudo yum install cpufreq-utils kernel-tools
# Fedora
sudo dnf install kernel-tools
- 查看当前状态
输出关键信息:
driver: 当前使用的驱动(intel_pstate,acpi-cpufreq)governor: 当前调频策略(powersave,performance,userspace)-
current policy: 支持的频率范围 -
设置为性能模式(推荐)
-
performance模式会尽可能保持最高频率(P0 State) -
(可选)手动锁定频率
# 锁定所有 CPU 到 3.5GHz
sudo cpupower frequency-set -f 3500MHz
# 或设置最小/最大频率范围
sudo cpupower frequency-set -d 3500MHz -u 3500MHz
- 永久生效(开机自动设置)
[Unit]
Description=Set CPU to Performance Mode
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/bin/cpupower frequency-set -g performance
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
验证节能是否已关闭
- 检查 C-State
- 观察
C0%是否接近 100%,其他 C-state(C1~C10)应为 0% -
Busy%应 ≈C0% -
检查频率是否稳定
-
所有核心频率应稳定在目标值(如 3500.000 MHz),无波动
-
检查调频驱动和策略
governor应为performancecurrent policy频率范围应窄或固定
替代方案
| 方法 | 最激进用法 | 作用说明 |
|---|---|---|
x86_energy_perf_policy |
sudo x86_energy_perf_policy performance |
强制所有 CPU 核心运行在最高性能模式,禁用动态调频节能 |
isolcpus + nohz_full + rcu_nocbs |
内核参数: isolcpus=2-7 nohz_full=2-7 rcu_nocbs=2-7 |
将 CPU 2-7 完全隔离,无定时中断、无内核调度干扰,专用于实时任务 |
tuned 服务 |
sudo tuned-adm profile latency-performance |
启用极致低延迟配置,关闭节能,优化调度与中断处理 |
powertop --auto-tune |
sudo powertop --auto-tune |
自动将所有子系统(CPU、磁盘、USB 等)设为高性能模式,适合性能压测 |
系统的NUMA拓扑
- 有多少个NUMA节点?
- 每个节点有多少个CPU核心?
- 每个节点有多少内存?
- 节点之间的互联带宽和延迟如何?
常用工具来查看NUMA拓扑:
numactl --hardware:显示NUMA节点的硬件信息,包括CPU数量和内存大小。lscpu:显示CPU架构信息,包括NUMA节点分布。hwloc:一个库和工具集,可以生成系统的详细拓扑图,包括NUMA节点、CPU、缓存、PCI设备等。
# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
node 0 size: 96920 MB
node 0 free: 2951 MB
node 1 cpus: 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
node 1 size: 98304 MB
node 1 free: 33 MB
node distances:
node 0 1
0: 10 21
1: 21 10
- CPU 被分成 node 0 和 node 1 两组(这台机器有两个 CPU Socket)。
- 一组 CPU 分配到 96 GB 的内存(这台机器总共有 192GB 内存)。
- node distances 是一个二维矩阵,
node[i][j]表示 node i 访问 node j 的内存的相对距离。比如 node 0 访问 node 0 的内存的距离是 10,而 node 0 访问 node 1 的内存的距离是 21。
# numactl --show
policy: default
preferred node: current
physcpubind: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
cpubind: 0 1
nodebind: 0 1
membind: 0 1
numactl 命令还有几个重要选项:
--cpubind=0:绑定到 node 0 的 CPU 上执行。--membind=1:只在 node 1 上分配内存。--interleave=nodes:nodes 可以是all、N,N,N或N-N,表示在 nodes 上轮循(round robin)分配内存。--physcpubind=cpus:cpus 是/proc/cpuinfo中的 processor(超线程)字段,cpus 的格式与--interleave=nodes一致,表示绑定到指定的 cpus 上运行。--preferred=1:优先从 node 1 上分配内存。
numactl 命令的几个例子:
# 运行 test_program 程序,参数是 argument,绑定到 node 0 的 CPU 和 node 1 的内存
numactl --cpubind=0 --membind=1 test_program arguments
# 在 processor 0-4,8-12 上运行 test_program
numactl --physcpubind=0-4,8-12 test_program arguments
# 轮询分配内存,将程序的内存页交错分配到所有 NUMA 节点,同时允许程序在所有 CPU 上运行。适用于对内存带宽要求高,数据访问模式随机的应用。
numactl --interleave=all test_program arguments
# 优先考虑从 node 1 上分配内存
numactl --preferred=1
1. 核心思想
在多CPU插槽的服务器上,每个CPU都有自己的本地内存,访问本地内存的速度远快于访问另一个CPU的内存(远程内存)。因此,优化的关键是确保“谁计算,谁的数据就在谁身边”。
高级场景(高性能网络)是cpu、内存、PCIe 设备(网卡)的一致。
2. Linux的内存分配行为
- 默认策略:总是优先在当前CPU核心所在的本地NUMA节点上分配内存。
- 例外情况:当本地内存不足时,行为由内核参数
vm.zone_reclaim_mode决定。 vm.zone_reclaim_mode = 0(默认值): 本地内存不足时,去远程节点寻找空闲内存,是大多数场景的推荐配置。vm.zone_reclaim_mode = 1: 本地内存不足时,优先回收本地不活跃的内存页(如Cache),而不是去访问远程内存。这个回收过程本身可能引入延迟。
3. 管理与优化工具
-
numactl(最常用):命令行工具,在启动应用时指定其NUMA策略。 -
强制绑定(
--membind):numactl --cpunodebind=0 --membind=0 my_app - 效果:强制
my_app只能在节点0的CPU上运行,且只能从节点0的内存分配。如果节点0内存耗尽,分配会失败。提供最强的性能确定性。 - 优先使用(
--preferred):numactl --preferred=0 my_app -
效果:优先从节点0分配,如果失败,则自动回退到其他节点。在保证程序可用性的同时进行优化。
-
sysctl(系统级调整): -
sudo sysctl -w vm.zone_reclaim_mode=0: 确保系统采用默认的NUMA分配行为,避免不必要的本地内存回收延迟。
# 永久修改
# 在 /etc/sysctl.conf 或 /etc/sysctl.d/下的新文件中添加一行:
# vm.zone_reclaim_mode = 0
# 然后执行 sudo sysctl -p 使其生效
-
cpuset(底层硬隔离): -
cpuset在分配CPU核心的同时,通过cpuset.mems文件也强制绑定了内存节点,是比numactl更底层的内核级隔离,效果与numactl --membind类似但隔离性更强。
4.NUMA之前
- 核心是“无共享”:采用无共享(Share-Nothing)架构,将系统划分为独立的计算单元,各自拥有专属的数据(数据分区)。从逻辑上根除了跨NUMA节点内存访问。
- 用“线程局部”取代“全局共享”:使用线程局部存储(TLS)来消除数据竞争和锁。每个线程只在自己的“领地”(即本地NUMA内存)上工作,避免因数据争用导致的跨节点缓存同步。
- 以“消息传递”代替“共享内存”:使用Actor模型、disruptor通信等模式,即CSP并发哲学。
NUMA优化是一个迭代的过程,需要持续的性能分析和监控来识别瓶颈并验证优化效果。
numastat: 实时或历史地查看每个 NUMA 节点的内存访问统计,包括本地(local_node)和远程(other_node)内存访问次数。如果远程访问比例很高,说明 NUMA 优化还有空间。
perf: Linux 下性能分析工具,可以跟踪各种硬件事件,例如缓存未命中(cache-misses)、远程内存访问(mem_load_retired.l3_miss等)。通过分析这些事件发现潜在的 NUMA 瓶颈。
- Intel VTune Amplifier: 对于 Intel CPU 的微架构性能分析工具,提供非常详细的 NUMA 相关的性能指标和优化建议。
考虑因素:
- 过度优化: 并非所有程序都需要 NUMA 优化。对于 CPU 密集型且数据局部性极强的程序,或者数据量很小、内存访问模式随机的程序,NUMA 优化可能收益不大甚至适得其反。
- 动态环境: 如果程序需要在不同 NUMA 拓扑的系统上运行,可能需要动态地检测 NUMA 节点并调整策略。
显式 NUMA 感知内存分配
以下是 libnuma 常用接口及相关系统调用表(API 以 numa(3) 与 numactl 上游 numa.h 为准;策略常量见 mbind(2)):
| 分类 | 函数签名 | 返回值类型 | 关键说明 |
|---|---|---|---|
| 初始化 | int numa_available(void); |
int |
必须先调用;返回 −1 则不应再使用本库其余接口(非 void) |
| API 版本 | #define LIBNUMA_API_VERSION |
宏 | 源码兼容用 API 版本号(当前为 2);无标准 numa_version() 函数 |
| 节点 / 拓扑 | int numa_max_node(void); |
int |
当前系统最大节点编号 |
int numa_max_possible_node(void); |
int |
内核可表示的节点上界相关 | |
int numa_num_configured_nodes(void); |
int |
配置的内存节点数 | |
int numa_num_possible_nodes(void); |
int |
内核 nodemask 可容纳的节点数 | |
int numa_num_configured_cpus(void); |
int |
系统 CPU 数 | |
int numa_num_possible_cpus(void); |
int |
内核 cpumask 可容纳的 CPU 数 | |
struct bitmask *numa_get_mems_allowed(void); |
struct bitmask * |
当前 cpuset 下允许分配内存的节点掩码 | |
int numa_num_task_cpus(void); |
int |
当前任务可用 CPU 数 | |
int numa_num_task_nodes(void); |
int |
当前任务可分配内存的节点数 | |
| 全局符号 | extern struct bitmask *numa_all_nodes_ptr; |
全局变量 | 当前任务可分配内存的节点集合(勿修改) |
extern struct bitmask *numa_nodes_ptr; |
全局变量 | 内核向用户态暴露的节点集合 | |
extern struct bitmask *numa_no_nodes_ptr; |
全局变量 | 空节点集 | |
extern struct bitmask *numa_all_cpus_ptr; |
全局变量 | 当前任务允许运行的 CPU 集合 | |
| 解析 | struct bitmask *numa_parse_nodestring(const char *s); |
struct bitmask * |
解析如 0-3,7、all、!1 |
struct bitmask *numa_parse_nodestring_all(const char *s); |
struct bitmask * |
同上,不限于当前 nodeset | |
struct bitmask *numa_parse_cpustring(const char *s); |
struct bitmask * |
解析 CPU 列表 | |
struct bitmask *numa_parse_cpustring_all(const char *s); |
struct bitmask * |
同上,不限于当前 cpuset | |
int numa_parse_bitmap(char *line, struct bitmask *mask); |
int |
解析 sysfs 风格位图(偏底层) | |
| 节点容量 | long numa_node_size(int node, long *freep); |
long |
节点内存;freep 可填空闲量(见本地 man) |
long long numa_node_size64(int node, long long *freep); |
long long |
64 位安全版本 | |
int numa_node_of_cpu(int cpu); |
int |
CPU 所属节点 | |
int numa_distance(int node1, int node2); |
int |
SLIT 距离(10 的倍数;失败返回 0) | |
int numa_pagesize(void); |
int |
页大小 | |
| 首选 / 多首选 | int numa_preferred(void); |
int |
当前首选节点 |
int numa_preferred_err(void); |
int |
首选不可用时的错误语义 | |
void numa_set_preferred(int node); |
void |
设置首选节点;node==-1 等价本地分配 |
|
int numa_has_preferred_many(void); |
int |
是否支持多节点首选 | |
void numa_set_preferred_many(struct bitmask *m); |
void |
多节点首选 | |
struct bitmask *numa_preferred_many(void); |
struct bitmask * |
查询多首选(调用者 numa_bitmask_free) |
|
int numa_has_home_node(void); |
int |
是否支持 home node | |
int numa_set_mempolicy_home_node(void *start, unsigned long len, int home_node, int flags); |
int |
为区间策略设置 home node | |
| 策略 / 绑定 | void numa_set_localalloc(void); |
void |
本地节点优先 |
void numa_set_interleave_mask(struct bitmask *m); |
void |
页交错掩码;空掩码关闭 | |
struct bitmask *numa_get_interleave_mask(void); |
struct bitmask * |
当前交错掩码 | |
void numa_set_weighted_interleave_mask(struct bitmask *m); |
void |
加权交错(内核权重见 sysfs) | |
struct bitmask *numa_get_weighted_interleave_mask(void); |
struct bitmask * |
当前加权交错掩码 | |
void numa_set_membind(struct bitmask *m); |
void |
仅自掩码内节点分配(原表误写为 numa_set_bind_mask) |
|
void numa_set_membind_balancing(struct bitmask *m); |
void |
membind 且可配合内核 NUMA balancing |
|
struct bitmask *numa_get_membind(void); |
struct bitmask * |
当前 membind 掩码 | |
void numa_bind(struct bitmask *m); |
void |
等价于跑在节点上 + numa_set_membind |
|
void numa_set_bind_policy(int strict); |
void |
绑定内存时严格/首选 | |
void numa_set_strict(int flag); |
void |
已映射页与策略不一致时是否报错 | |
| 分配 | void *numa_alloc_onnode(size_t size, int node); |
void * |
指定节点分配 |
void *numa_alloc_local(size_t size); |
void * |
本地节点 | |
void *numa_alloc_interleaved(size_t size); |
void * |
全节点交错 | |
void *numa_alloc_interleaved_subset(size_t size, struct bitmask *m); |
void * |
子集交错 | |
void *numa_alloc_weighted_interleaved(size_t size); |
void * |
全节点加权交错 | |
void *numa_alloc_weighted_interleaved_subset(size_t size, struct bitmask *m); |
void * |
子集加权交错 | |
void *numa_alloc(size_t size); |
void * |
按当前任务策略分配 | |
void *numa_realloc(void *old, size_t oldsz, size_t newsz); |
void * |
调整大小并尽量保持策略 | |
void numa_free(void *ptr, size_t size); |
void |
释放 numa_alloc_* |
|
| 区间 / 迁移 | void numa_interleave_memory(void *p, size_t sz, struct bitmask *m); |
void |
已映射未触碰区域的页交错 |
void numa_weighted_interleave_memory(void *p, size_t sz, struct bitmask *m); |
void |
加权页交错 | |
void numa_tonode_memory(void *p, size_t sz, int node); |
void |
将页放到指定节点 | |
void numa_tonodemask_memory(void *p, size_t sz, struct bitmask *m); |
void |
放到节点集合 | |
void numa_setlocal_memory(void *p, size_t sz); |
void |
放到当前运行节点 | |
void numa_police_memory(void *p, size_t sz); |
void |
按当前策略落实(有 RMW,注意并发) | |
int numa_move_pages(pid_t pid, unsigned long count, void **pages, const int *nodes, int *status, int flags); |
int |
封装 move_pages(2) |
|
int numa_migrate_pages(pid_t pid, struct bitmask *from, struct bitmask *to); |
int |
封装 migrate_pages(2) |
|
| CPU 亲和 | int numa_run_on_node(int node); |
int |
跑在指定节点;-1 恢复 |
int numa_run_on_node_mask(struct bitmask *m); |
int |
跑在节点掩码上 | |
int numa_run_on_node_mask_all(struct bitmask *m); |
int |
同上,不考虑 cpuset | |
struct bitmask *numa_get_run_node_mask(void); |
struct bitmask * |
当前可运行节点掩码 | |
int numa_node_to_cpus(int node, struct bitmask *mask); |
int |
填入已分配的 cpumask,非返回指针 | |
void numa_node_to_cpu_update(void); |
void |
CPU 热插拔后刷新 | |
int numa_sched_getaffinity(pid_t pid, struct bitmask *mask); |
int |
封装 sched_getaffinity |
|
int numa_sched_setaffinity(pid_t pid, struct bitmask *mask); |
int |
封装 sched_setaffinity |
|
struct bitmask *numa_allocate_cpumask(void); |
struct bitmask * |
分配 cpumask | |
void numa_free_cpumask(struct bitmask *b); |
void |
释放 cpumask | |
| 位图 | struct bitmask *numa_bitmask_alloc(unsigned int n); |
struct bitmask * |
分配 n 位 |
void numa_bitmask_free(struct bitmask *b); |
void |
释放 | |
struct bitmask *numa_bitmask_setall(struct bitmask *b); |
struct bitmask * |
全 1(头文件多为返回指针) | |
struct bitmask *numa_bitmask_clearall(struct bitmask *b); |
struct bitmask * |
全 0 | |
struct bitmask *numa_bitmask_setbit(struct bitmask *b, unsigned i); |
struct bitmask * |
置位 | |
struct bitmask *numa_bitmask_clearbit(struct bitmask *b, unsigned i); |
struct bitmask * |
清位 | |
int numa_bitmask_isbitset(const struct bitmask *b, unsigned i); |
int |
测试位(标准名,非 is_set) |
|
int numa_bitmask_equal(const struct bitmask *a, const struct bitmask *b); |
int |
相等 | |
unsigned int numa_bitmask_weight(const struct bitmask *b); |
unsigned int |
置位数 | |
unsigned int numa_bitmask_nbytes(const struct bitmask *b); |
unsigned int |
掩码字节数 | |
struct bitmask *numa_allocate_nodemask(void); |
struct bitmask * |
节点掩码 | |
void numa_free_nodemask(struct bitmask *b); |
void |
释放节点掩码 | |
void copy_bitmask_to_nodemask(...); 等 |
void |
与 nodemask_t 互拷 |
|
| 错误处理 | void numa_error(char *where); |
void |
可覆盖 |
void numa_warn(int n, char *fmt, ...); |
void |
可覆盖 | |
extern int numa_exit_on_error; 等 |
int |
遇错/警告是否退出等 | |
| 大页与映射 | void *mmap(...); |
void * |
MAP_HUGETLB 等大页 |
| NUMA 系统调用 | long mbind(void *addr, unsigned long len, int mode, const unsigned long *nodemask, unsigned long maxnode, unsigned int flags); |
long |
区间策略;#include <numaif.h>,链接 -lnuma |
long set_mempolicy(int mode, const unsigned long *nmask, unsigned long maxnode); |
long |
每线程默认策略 | |
long get_mempolicy(int *mode, unsigned long *nmask, unsigned long maxnode, void *addr, int flags); |
long |
查询策略 / 页所在节点 | |
long move_pages(...); |
long |
迁移指定用户页 | |
long migrate_pages(...); |
long |
按节点集迁移 | |
int getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache); |
int |
当前 CPU/节点 |
mbind / set_mempolicy 常用策略常量:MPOL_DEFAULT、MPOL_BIND、MPOL_INTERLEAVE、MPOL_WEIGHTED_INTERLEAVE、MPOL_PREFERRED、MPOL_PREFERRED_MANY、MPOL_LOCAL;标志位如 MPOL_MF_MOVE、MPOL_MF_STRICT 等见 mbind(2)。
示例:在特定节点上分配内存
#define _GNU_SOURCE // 启用 GNU 扩展,包括 sched_setaffinity
#include <numa.h> // 需要链接 libnuma
#include <pthread.h>
#include <sched.h>
#include <iostream>
#include <thread>
#include <vector>
void worker_function(int thread_id, int target_cpu) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(target_cpu, &cpuset);
if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) {
std::cerr << "Error setting CPU affinity for thread " << thread_id
<< " to CPU " << target_cpu << std::endl;
} else {
std::cout << "Thread " << thread_id << " bound to CPU " << target_cpu
<< std::endl;
}
// 模拟一些工作,例如访问之前分配在特定NUMA节点上的数据
long long sum = 0;
for (int i = 0; i < 100000000; ++i) {
sum += i;
}
std::cout << "Thread " << thread_id << " finished work, sum: " << sum
<< std::endl;
}
int main() {
if (numa_available() == -1) {
std::cerr << "NUMA support not available." << std::endl;
return 1;
}
int num_nodes = numa_num_configured_nodes();
if (num_nodes < 1) {
std::cerr << "No NUMA nodes detected." << std::endl;
return 1;
}
std::vector<std::thread> threads;
int current_cpu = 0;
for (int i = 0; i < num_nodes; ++i) {
// 先分配 cpumask,再用 numa_node_to_cpus 填入该节点上的 CPU
struct bitmask* cpus_on_node = numa_allocate_cpumask();
if (numa_node_to_cpus(i, cpus_on_node) < 0) {
std::cerr << "Failed to get CPUs for node " << i << std::endl;
numa_free_cpumask(cpus_on_node);
continue;
}
for (int cpu = 0; cpu < numa_num_configured_cpus(); ++cpu) {
if (numa_bitmask_isbitset(cpus_on_node, cpu)) {
threads.emplace_back(worker_function, current_cpu++, cpu);
}
}
numa_free_cpumask(cpus_on_node);
}
for (auto& t : threads) {
t.join();
}
std::cout << "All threads finished." << std::endl;
return 0;
}
编译: g++ -std=c++11 test.cpp -o test -lnuma -pthread
运行: ./test
NUMA数据分片
数据分片的核心思想是根据数据的访问频率、重要性以及策略逻辑,将不同类型的数据结构分配到最合适的物理内存区域。
# 查看NUMA节点分布
numactl --hardware
# 为Node0分配1024个2MB大页
echo 1024 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
# 为Node1分配512个1GB大页(若支持)
echo 512 > /sys/devices/system/node/node1/hugepages/hugepages-1048576kB/nr_hugepages
#include <numa.h>
#include <numaif.h>
#include <sys/mman.h>
void* alloc_hugepage_on_node(int node, size_t size) {
struct bitmask* nm = numa_allocate_nodemask();
if (!nm) return NULL;
numa_bitmask_setbit(nm, node);
void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap hugepage failed");
numa_free_nodemask(nm);
return NULL;
}
if (mbind(ptr, size, MPOL_BIND, nm->maskp, nm->size + 1, 0) == -1) {
perror("mbind failed");
munmap(ptr, size); // 绑定失败需释放已分配的大页
numa_free_nodemask(nm);
return NULL;
}
numa_free_nodemask(nm);
return ptr;
}
int main() {
const size_t memSize = 2 * 1024 * 1024; // 2MB
void* lockMem = alloc_hugepage_on_node(0, memSize);
// 锁定内存
if (-1 == mlock(lockMem, memSize)) {
munmap(lockMem, memSize);
}
// 手动触发缺页中断
memset(lockMem, 0, memSize);
// 保持常驻(程序运行时内存不会换出)
while (true) {
// 实际应用中应有退出逻辑
sleep(1);
}
// 程序退出时自动解锁
munlock(lockMem, memSize);
munmap(lockMem, memSize);
return 0;
}