3、CPU亲和性(Affinity)概念

3.1 什么是CPU亲和性

CPU亲和性(CPU Affinity)是指将进程或线程绑定到特定CPU核心(或一组核心)上运行的机制。它决定了操作系统调度器在分配CPU时间片时,是否允许进程在多个核心之间自由迁移,还是强制固定在某个核心上执行。

从实现层面看,CPU亲和性通过位掩码(bitmask)表示,每个bit对应一个逻辑CPU核心。当位掩码中某位置1时,表示允许进程在该核心上运行;置0则表示禁止。

核心定义: CPU亲和性 = 进程与CPU核心之间的绑定关系,控制调度器将进程安排在哪些核心上执行。
CPU亲和性概念示意图 无亲和性(自由迁移) 进程A 进程B 进程C CPU0 CPU1 CPU2 进程可在核心间自由迁移 有亲和性(绑定核心) 进程X 进程Y 进程Z CPU0 CPU1 CPU2 进程固定绑定到指定核心

3.2 软亲和性与硬亲和性的区别

CPU亲和性根据实现机制和强制程度,分为软亲和性(Soft Affinity)和硬亲和性(Hard Affinity)两种类型。两者在调度行为、系统开销和适用场景上存在本质差异。

对比维度 软亲和性(Soft Affinity) 硬亲和性(Hard Affinity)
实现机制 调度器尽力将进程留在同一核心,但不强制 通过系统调用(sched_setaffinity)强制绑定
强制程度 建议性(best-effort) 强制性(mandatory)
迁移条件 负载均衡时可迁移到其他核心 禁止迁移到非绑定核心
系统调用 无(内核自动管理) sched_setaffinity / sched_getaffinity
CPU缓存利用率 较高(大部分时间命中) 最高(始终命中)
调度灵活性 高(可应对负载变化) 低(可能造成核心负载不均)
适用场景 通用进程、交互式应用 实时任务、高性能计算、DPDK
实践建议: 大多数通用服务使用软亲和性即可获得较好的缓存局部性。只有当延迟敏感度极高(如网络中断处理、实时音频)或需要彻底隔离CPU资源时,才使用硬亲和性。
软亲和性 vs 硬亲和性 调度决策流程 软亲和性流程 进程上次运行在CPU0 调度器检查负载均衡 CPU0空闲 → 留在CPU0 CPU0过载 → 迁移到CPU1 硬亲和性流程 进程绑定CPU0(位掩码) 调度器检查亲和性掩码 CPU0可用 → 调度到CPU0 CPU0繁忙 → 进程等待

3.3 为什么需要设置CPU亲和性

在多核系统中,不设置CPU亲和性会导致进程频繁在核心间迁移,带来显著的性能开销。设置CPU亲和性的核心动机包括以下五个方面:

  1. 减少缓存未命中(Cache Miss): 进程迁移到新核心后,L1/L2缓存内容失效,需要重新加载数据,导致内存访问延迟增加。
  2. 降低TLB刷新开销: 核心切换会刷新TLB(Translation Lookaside Buffer),增加页表遍历次数。
  3. 消除NUMA跨节点访问: 在NUMA架构中,进程迁移到远程节点会导致内存访问延迟大幅上升(通常增加1.5~3倍)。
  4. 保障实时性/确定性: 实时任务需要可预测的调度延迟,绑定核心可避免被其他任务抢占或迁移。
  5. 隔离干扰任务: 将高负载任务与关键任务隔离到不同核心,防止相互干扰。
性能陷阱: 不设置亲和性时,进程在4个核心间迁移一次,平均增加约 0.5~2μs 的缓存预热开销。对于每秒处理百万级数据包的网络应用,这种开销会累积为显著的吞吐量下降。
设置CPU亲和性的五大收益 减少缓存未命中 L1/L2命中率↑ 内存延迟↓ 降低TLB刷新 页表遍历↓ 地址转换加速 消除NUMA跨节点 本地内存访问 延迟降低50%+ 保障实时性 调度延迟可预测 抖动↓ 隔离干扰任务 关键任务不受扰 稳定性↑ 系统吞吐量提升 延迟降低 · 稳定性增强

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%。