跳转至

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 内核参数

  1. 编辑 GRUB 配置
sudo vim /etc/default/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 ,便于手动控制频率
  1. 更新 GRUB 配置
# Debian/Ubuntu
sudo update-grub

# CentOS/RHEL/Fedora
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
  1. 重启生效
sudo reboot

Linux 运行时控制

  1. 安装频率控制工具
# Ubuntu/Debian
sudo apt install cpufrequtils linux-tools-common

# CentOS/RHEL
sudo yum install cpufreq-utils kernel-tools

# Fedora
sudo dnf install kernel-tools
  1. 查看当前状态
cpupower frequency-info

输出关键信息:

  • driver: 当前使用的驱动(intel_pstate, acpi-cpufreq)
  • governor: 当前调频策略(powersave, performance, userspace)
  • current policy: 支持的频率范围

  • 设置为性能模式(推荐)

sudo cpupower frequency-set -g performance
  • performance 模式会尽可能保持最高频率(P0 State)

  • (可选)手动锁定频率

# 锁定所有 CPU 到 3.5GHz
sudo cpupower frequency-set -f 3500MHz

# 或设置最小/最大频率范围
sudo cpupower frequency-set -d 3500MHz -u 3500MHz
  1. 永久生效(开机自动设置)
sudo vim /etc/systemd/system/cpu-performance.service
[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
sudo systemctl enable cpu-performance.service
sudo systemctl start cpu-performance.service

验证节能是否已关闭

  1. 检查 C-State
# 查看当前空闲状态
cat /proc/cpuinfo | grep -i "idle"

# 使用 turbostat(需 root)
sudo turbostat --interval 5
  • 观察 C0% 是否接近 100%,其他 C-state(C1~C10)应为 0%
  • Busy% 应 ≈ C0%

  • 检查频率是否稳定

watch -n 0.5 'cat /proc/cpuinfo | grep "cpu MHz"'
  • 所有核心频率应稳定在目标值(如 3500.000 MHz),无波动

  • 检查调频驱动和策略

cpupower frequency-info
  • governor 应为 performance
  • current 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
  1. CPU 被分成 node 0 和 node 1 两组(这台机器有两个 CPU Socket)。
  2. 一组 CPU 分配到 96 GB 的内存(这台机器总共有 192GB 内存)。
  3. 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 命令还有几个重要选项:

  1. --cpubind=0:绑定到 node 0 的 CPU 上执行。
  2. --membind=1:只在 node 1 上分配内存。
  3. --interleave=nodes:nodes 可以是 all、N,N,N 或 N-N,表示在 nodes 上轮循(round robin)分配内存。
  4. --physcpubind=cpus:cpus 是 /proc/cpuinfo 中的 processor(超线程)字段,cpus 的格式与 --interleave=nodes 一致,表示绑定到指定的 cpus 上运行。
  5. --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. 核心思想

NUMA 节点与 CPU、内存的拓扑关系

在多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. 管理与优化工具

  1. numactl(最常用):命令行工具,在启动应用时指定其NUMA策略。

  2. 强制绑定(--membind):numactl --cpunodebind=0 --membind=0 my_app

  3. 效果:强制 my_app 只能在节点0的CPU上运行,且只能从节点0的内存分配。如果节点0内存耗尽,分配会失败。提供最强的性能确定性。
  4. 优先使用(--preferred):numactl --preferred=0 my_app
  5. 效果:优先从节点0分配,如果失败,则自动回退到其他节点。在保证程序可用性的同时进行优化。

  6. sysctl(系统级调整):

  7. sudo sysctl -w vm.zone_reclaim_mode=0: 确保系统采用默认的NUMA分配行为,避免不必要的本地内存回收延迟。

# 永久修改
# 在 /etc/sysctl.conf 或 /etc/sysctl.d/下的新文件中添加一行:
# vm.zone_reclaim_mode = 0
# 然后执行 sudo sysctl -p 使其生效
  1. cpuset(底层硬隔离):

  2. 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 优化还有空间。
numastat -m # 查看内存策略
numastat -c # 查看每个CPU的内存访问统计
numastat -p <pid> # 查看特定进程的NUMA统计
  • perf: Linux 下性能分析工具,可以跟踪各种硬件事件,例如缓存未命中(cache-misses)、远程内存访问(mem_load_retired.l3_miss 等)。通过分析这些事件发现潜在的 NUMA 瓶颈。
perf stat -e cache-misses,L1-dcache-loads,L1-dcache-misses ./your_program
  • 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;
}