3、CPU亲和性(Affinity)概念
3.1 什么是CPU亲和性
CPU亲和性(CPU Affinity)是指将进程或线程绑定到特定CPU核心(或一组核心)上运行的机制。它决定了操作系统调度器在分配CPU时间片时,是否允许进程在多个核心之间自由迁移,还是强制固定在某个核心上执行。
从实现层面看,CPU亲和性通过位掩码(bitmask)表示,每个bit对应一个逻辑CPU核心。当位掩码中某位置1时,表示允许进程在该核心上运行;置0则表示禁止。
核心定义: CPU亲和性 = 进程与CPU核心之间的绑定关系,控制调度器将进程安排在哪些核心上执行。
3.2 软亲和性与硬亲和性的区别
CPU亲和性根据实现机制和强制程度,分为软亲和性(Soft Affinity)和硬亲和性(Hard Affinity)两种类型。两者在调度行为、系统开销和适用场景上存在本质差异。
| 对比维度 | 软亲和性(Soft Affinity) | 硬亲和性(Hard Affinity) |
|---|---|---|
| 实现机制 | 调度器尽力将进程留在同一核心,但不强制 | 通过系统调用(sched_setaffinity)强制绑定 |
| 强制程度 | 建议性(best-effort) | 强制性(mandatory) |
| 迁移条件 | 负载均衡时可迁移到其他核心 | 禁止迁移到非绑定核心 |
| 系统调用 | 无(内核自动管理) | sched_setaffinity / sched_getaffinity |
| CPU缓存利用率 | 较高(大部分时间命中) | 最高(始终命中) |
| 调度灵活性 | 高(可应对负载变化) | 低(可能造成核心负载不均) |
| 适用场景 | 通用进程、交互式应用 | 实时任务、高性能计算、DPDK |
实践建议: 大多数通用服务使用软亲和性即可获得较好的缓存局部性。只有当延迟敏感度极高(如网络中断处理、实时音频)或需要彻底隔离CPU资源时,才使用硬亲和性。
3.3 为什么需要设置CPU亲和性
在多核系统中,不设置CPU亲和性会导致进程频繁在核心间迁移,带来显著的性能开销。设置CPU亲和性的核心动机包括以下五个方面:
- 减少缓存未命中(Cache Miss): 进程迁移到新核心后,L1/L2缓存内容失效,需要重新加载数据,导致内存访问延迟增加。
- 降低TLB刷新开销: 核心切换会刷新TLB(Translation Lookaside Buffer),增加页表遍历次数。
- 消除NUMA跨节点访问: 在NUMA架构中,进程迁移到远程节点会导致内存访问延迟大幅上升(通常增加1.5~3倍)。
- 保障实时性/确定性: 实时任务需要可预测的调度延迟,绑定核心可避免被其他任务抢占或迁移。
- 隔离干扰任务: 将高负载任务与关键任务隔离到不同核心,防止相互干扰。
性能陷阱: 不设置亲和性时,进程在4个核心间迁移一次,平均增加约 0.5~2μs 的缓存预热开销。对于每秒处理百万级数据包的网络应用,这种开销会累积为显著的吞吐量下降。
3.4 亲和性设置示例
在Linux系统中,通过 sched_setaffinity 系统调用或 taskset 命令行工具设置CPU亲和性。以下是一个C语言示例,将当前进程绑定到CPU0和CPU2:
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
int main() {
cpu_set_t mask;
CPU_ZERO(&mask); // 清空掩码
CPU_SET(0, &mask); // 允许CPU0
CPU_SET(2, &mask); // 允许CPU2
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
perror("sched_setaffinity");
return 1;
}
printf("进程 %d 已绑定到 CPU0 和 CPU2\n", getpid());
// 验证当前亲和性
cpu_set_t get_mask;
CPU_ZERO(&get_mask);
if (sched_getaffinity(0, sizeof(get_mask), &get_mask) == 0) {
for (int i = 0; i < CPU_SETSIZE; i++) {
if (CPU_ISSET(i, &get_mask)) {
printf(" 允许在 CPU%d 上运行\n", i);
}
}
}
while (1); // 保持运行
return 0;
}
使用 taskset 命令行工具绑定进程:
# 启动时绑定到CPU0和CPU2
taskset -c 0,2 ./myapp
# 绑定已运行的进程(PID=1234)到CPU1-3
taskset -p -c 1-3 1234
# 查看进程当前亲和性
taskset -p 1234
pid 1234's current affinity mask: e
# 输出说明:e = 0b1110,表示允许CPU1、CPU2、CPU3
关键指标: 设置CPU亲和性后,典型场景下L1缓存命中率可从85%提升至95%以上,应用延迟抖动(jitter)可降低60%~80%。