当前位置: 首页 > news >正文

CPU 亲和性

CPU 亲和性本质

CPU 亲和性 = 让进程 / 线程只在指定的 CPU 核心上运行的调度约束。

内核里叫:sched_affinity(调度亲和性)

作用:

  1. 提高L1/L2/L3 缓存命中率
  2. 减少上下文切换(context switch)
  3. 避免跨 NUMA 节点访问
  4. 关键任务独占核心、降低抖动
  5. 低延迟系统必备(数据库、网关、实时业务)

内核核心结构:cpu_set_t

所有亲和性 API 都围绕这个结构:

typedef struct { unsigned long __bits[CPU_SETSIZE / sizeof(long)]; } cpu_set_t;
  • 本质:bitmask(位掩码)
  • 每一位代表一个 CPU 核心
  • 位 = 1 → 允许运行在该核心
  • 位 = 0 → 禁止运行

标准宏

CPU_ZERO(&set); // 清空所有位 CPU_SET(cpu, &set); // 将 cpu 号对应的位置1 CPU_CLR(cpu, &set); // 清位 CPU_ISSET(cpu, &set); // 判断是否允许该CPU

关键硬件常识

1. 物理核心 vs 超线程(HT)

  • 一个物理核心 = 一套 L1/L2
  • 超线程只是共享执行单元
  • 两个超线程(如 0 和 8)共享 L1/L2

结论:不要把不同业务线程绑到同一个物理核心的超线程上!会互相污染缓存,互相拖慢。

2. NUMA 节点

  • 每个 NUMA 节点有自己的内存控制器 + L3 分区
  • 线程跨 NUMA 节点 → 访问远端内存 → 延迟暴增→绑核必须 + 绑内存节点

两个最核心系统调用

1. 进程亲和性

int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask); int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
  • pid = 0→ 调用自身进程
  • sched_setaffinity 只限制 “能在哪些 CPU 上跑”,不限制 “在这些 CPU 之间切换”!
  • 固定在一个 CPU,永不迁移!亲和性 mask 只给 1 个 CPU
  • 极少数场景,绑单核也会迁移

    • CPU 热插拔
    • 内核负载均衡强制迁移(极少)
    • SCHED_IDLE、rt 任务压挤
  • 终极方案:isolcpus 内核隔离

2. 线程亲和性(Linux 专有)

int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset); int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);

区别:

进程亲和性:子进程会继承

线程亲和性:只对当前线程生效,更精细

最标准可直接用的 C 代码

绑定当前线程到单个 CPU

#include <pthread.h> #include <sched.h> int bind_thread_to_cpu(int cpu) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); return pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); }

绑定当前进程到 CPU 0,1,2

int bind_process_to_cpus(int cpu_cnt, int *cpus) { cpu_set_t cpuset; CPU_ZERO(&cpuset); for (int i = 0; i < cpu_cnt; i++) CPU_SET(cpus[i], &cpuset); return sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); }

命令行工具:taskset

查看进程亲和性(十六进制掩码)

taskset -p 1234

查看友好格式(核心列表)

taskset -cp 1234

绑定进程到 CPU 1

taskset -c 1 -p 1234

启动时绑定

taskset -c 0-3 ./app

-cCPU 列表模式(人类友好)不加-c十六进制位掩码(内核原生)

软亲和性 vs 硬亲和性

软亲和性(默认)

内核尽量让进程在同一个 CPU 运行,但不强制

无法手动设置!内核自动管理,你只能观察。

硬亲和性(affinity mask)

强制只能在 mask 内的 CPU 运行我们平时说的 “绑核” 就是这个。

可以手动设置!通过sched_setaffinity/taskset强制绑定。

sched_setaffinity / pthread_setaffinity_np / taskset / isolcpus

原理

  • 用户提供cpu_mask 位掩码
  • 内核严格遵守线程永远只能在 mask 标记的 CPU 上运行
  • 任何情况不允许跨出 mask(除非内核 BUG)

特点

  1. 强制约束(hard constraint)
  2. 用户完全控制CPU 集合
  3. 内核无权违反
  4. 线程不会被自动迁移
  5. 这才是真正的CPU 绑核

作用

  • 彻底杜绝线程迁移
  • L1/L2 缓存长期有效 → 命中率大幅提升
  • 降低延迟、消除抖动
  • 实现核心独占、资源隔离

内核默认行为

Linux 调度器(CFS)天生自带不需要任何配置

原理

  • 线程运行完一次时间片后
  • 内核优先选择上次的 CPU重新调度
  • 目标:提高缓存命中率

特点

  1. 非强制:负载不均衡时,内核会主动迁移线程
  2. 完全由调度器决定
  3. 用户无法控制
  4. 对应用透明
  5. 只能算优化策略,不是约束

内核里的专业叫法

cache-hot scheduling、cache affinity

优点

  • 自动负载均衡
  • 简单、无需配置

缺点

  • 高负载下线程频繁迁移
  • L1/L2 缓存频繁失效
  • 延迟不稳定、抖动大

内核源码层面的本质区别

软亲和性

内核在select_task_rq()里:优先选上次 CPU,但不是必须。

硬亲和性

内核在pick_next_task()/migrate_task()里:严格检查 p->cpus_mask ,只允许在 mask 内选择 CPU。

硬亲和性是硬件级限制,软亲和性只是调度偏好

NUMA 与亲和性

  • CPU 访问本地节点内存最快
  • 跨节点访问延迟高 30%~300%

绑核必须配合绑内存:

numactl --cpunodebind=0 --membind=0 ./app

内核级隔离:isolcpus(最强隔离)

grub 中添加: isolcpus=1,2,3

效果:

  • 这些 CPU完全不参与普通调度
  • 只有显式taskset/pthread_setaffinity才能使用
  • 无任何系统线程、中断、内核线程干扰

提高缓存命中率

现代 CPU 结构:核心 → L1/L2(核心私有) → L3(共享) → 内存

关键点:

  1. L1、L2 是每个核心私有的!
  2. 进程 / 线程从 CPU0 跑到 CPU1
    • L1/L2 全部失效
    • 必须重新从内存 / LLC 加载数据→cache miss 暴增,速度下降几倍~几十倍

操作系统默认行为:为了负载均衡,会主动把线程在核心间迁移

→ 这就是缓存命中率低的元凶

把线程固定在一个核心,不迁移 → L1/L2 数据一直保留 → 命中率大幅提升。

效果:

  • 缓存 miss 下降30%–90%
  • 平均指令延迟下降 20%–50%
  • 波动(jitter)几乎消失

如何绑才能最大化缓存

方法 1:单线程 → 绑死 1 个核心(最强缓存收益)

taskset -c 0 ./app

原理:

  • 线程永远在 CPU0
  • L1/L2 数据永远不失效
  • 缓存命中率接近理论上限

这是缓存最优结构

方法 2:多线程 → 每个线程绑独立物理核心

线程 A → CPU0线程 B → CPU1线程 C → CPU2线程 D → CPU3

每个线程独占一个物理核心的 L1/L2互不污染,缓存效率最高。

方法 3:进程内多线程 → 代码级绑核(最有效)

int bind_thread_to_cpu(int cpu) { cpu_set_t cs; CPU_ZERO(&cs); CPU_SET(cpu, &cs); return pthread_setaffinity_np(pthread_self(), sizeof(cs), &cs); }

每个工作线程启动时绑定自己的核心。

方法 4:NUMA 下必须:核心 + 内存 一起绑

numactl --cpunodebind=0 --membind=0 ./app
  • 线程在 node0 核心
  • 内存也从 node0 分配
  • L3 + 本地内存最优

方法 5:内核隔离 isolcpus(无任何污染)

isolcpus=1-4

这些核心不跑任何系统线程、中断、内核线程→ 缓存完全给你的业务用→缓存命中率最高、最稳定

核心要点

  • 线程绑核 > 进程绑核

    • 多线程必须每个线程独立绑核才能最大化缓存收益。

  • 不要绑超线程兄弟核心

    • 物理核 0 → 逻辑 0、8

    • 绑 0 就够了,不要绑 0,8,会争抢执行单元。

  • 亲和性是继承的

    • fork 子进程继承父进程亲和性
    • pthread 创建的线程不继承,必须自己绑
  • 中断亲和性 irq affinity

    • 网卡 / 磁盘中断也能绑核:/proc/irq/xxx/smp_affinity

  • 绑一个物理核心的两个超线程(0+8)→ L1/L2 共享,互相污染
  • 绑一堆核心(0-7),等于没绑→ 内核依然会迁移,缓存依然失效
  • 只绑核,不绑 NUMA 内存→ 跨节点访问,延迟高
  • 进程绑核,但线程不绑→ 线程依然乱跑

CPU 亲和性就是:用 bitmask 限制进程 / 线程只能跑在哪些核心,让缓存不失效、切换变少、延迟最低。

http://www.cnnetsun.cn/news/1602316.html

相关文章:

  • 微服务架构最佳实践:2025 实战指南
  • 基于stm32的智能体重秤设计[单片机]-计算机毕业设计源码+LW文档
  • C++:跳表
  • AI 开发实战:需求池越堆越乱,先让 AI 帮你做一轮梳理
  • 如何轻松地将三星手机中的照片传输到电脑?
  • Ceph存储集群搭建:如何选择RAID卡模式(HBA vs IT vs non-RAID)
  • 终极指南:如何在Windows 10上免费安装Android子系统
  • GHelper:实现华硕笔记本高效硬件控制的轻量级工具解决方案
  • Qwen3.5-9B GPU算力适配方案:A10/A100/V100显存占用与吞吐量对比
  • 影刀RPA × AI大语言模型:解锁企业智能自动化的无限潜能
  • JeecgBoot:AI驱动的企业级低代码平台高效构建与智能生成实践
  • VSCode的Partial Diff插件:轻松比对任意两段代码
  • 告别文献堆砌!PaperXie AI 文献综述:重构学术写作逻辑,3 步打造导师青睐的深度综述
  • 别再硬熬毕业论文!Paperxie AI:3 天搞定初稿,查重率直接降到 10% 内
  • AI辅助开发:让快马平台Kimi模型帮你构建《构石》官网智能搜索功能
  • AI编程新范式:GME-Qwen2-VL-2B辅助代码生成与视觉逻辑理解
  • Phi-3-mini-4k-instruct-gguf环境部署:CUDA推理路线+隔离venv最佳实践
  • 量子修道会:在纠缠态保存人类技术
  • LIMS实验室质量管理系统项目概述
  • 【DexGraspNet与多指手抓取算法详解】第二章 硬件平台与运动学建模
  • 基于ENSP的校园网多校区互联与安全防护实战设计
  • 大麦抢票终极指南:如何用开源工具告别手速焦虑
  • 基于COMSOL相场法与随机孔隙结构的驱替模拟案例解析
  • UDS诊断自动化测试入门:用Python模拟Tester端,批量刷写DID与安全访问
  • 解决Calibre中文路径乱码:让电子书管理回归本土语言
  • 保姆级教程:用Go的net/smtp包绕过第三方库,稳定发送QQ邮件到Gmail
  • 怎么部署自己的AI聊天机器人:从入门到落地全指南(避坑版)
  • 书匠策AI:毕业论文的“全能魔法师”,一键解锁学术新境界
  • 2026年三维扫描仪选购指南:五大靠谱厂家深度解析与避坑秘籍
  • onnx之fp16转换,int8量化