文章系转载,便于整理和分类,原文地址:https://zhuanlan.zhihu.com/p/38541212
一、准备知识:
并行:两件(多件)事情在同一时刻一起发生。
并发:两件(多件)事情在同一时刻只能有一个发生,由于CPU快速切换,从而给人的感觉是同时进行。
单核多线程:单核CPU上运行多线程, 同一时刻只有一个线程在跑,系统进行线程切换,系统给每个线程分配时间片来执行,看起来就像是同时在跑, 但实际上是每个线程跑一点点就换到其它线程继续跑。
多核多线程:每个核上各自运行线程,同一时刻可以有多个线程同时在跑。
物理CPU:机器上实际安装的CPU个数,比如说你的主板上安装了一块8核CPU,那么物理CPU个数就是1个,所以物理CPU个数就是主板上安装的CPU个数。
逻辑CPU:一般情况,我们认为一颗CPU可以有多个核,加上intel的超线程技术(HT), 可以在逻辑上再分一倍数量的CPU core出来。
超线程技术(Hyper-Threading):就是利用特殊的硬件指令,把两个逻辑CPU模拟成两个物理CPU,实现多核多线程。我们常听到的双核四线程/四核八线程指的就是支持超线程技术的CPU。
逻辑CPU数量 = 物理CPU数量 x CPU cores x 2(如果支持并开启HT) 比如:你的电脑安装了一块4核CPU,并且支持且开启了超线程(HT)技术,那么逻辑CPU数量 = 1 × 4 × 2 = 8
二、CPU的亲和性(affinity)
简单地说,CPU 亲和性(affinity)就是进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。
软亲和性(affinity): 就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。
2.6 版本的 Linux 内核还包含了一种机制,它让开发人员可以编程实现硬 CPU 亲和性(affinity)。这意味着应用程序可以显式地指定进程在哪个(或哪些)处理器上运行。
三、什么是 Linux 内核硬亲和性(affinity)?
硬亲和性(affinity):简单来说就是利用linux内核提供给用户的API,强行将进程或者线程绑定到某一个指定的cpu核运行。
在 Linux 内核中,所有的进程都有一个相关的数据结构,称为 task_struct *。*这个结构非常重要,原因有很多;其中与 亲和性(affinity)相关度最高的是cpus_allowed 位掩码。这个位掩码由n位组成,与系统中的n个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。
如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 CPU 上运行。因此,如果一个进程可以在任何 CPU 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上,这就是 Linux 中进程的缺省状态。
Linux 内核 API 提供了一些方法,让用户可以修改位掩码或查看当前的位掩码:
sched_set_affinity()
(用来修改位掩码)sched_get_affinity()
(用来查看当前的位掩码)
注意,cpu_affinity
会被传递给子线程,因此应该适当地调用 sched_set_affinity
。
四、为什么应该使用硬亲和性(affinity)?
通常 Linux 内核都可以很好地对进程进行调度,在应该运行的地方运行进程(这就是说,在可用的处理器上运行并获得很好的整体性能)。内核包含了一些用来检测 CPU 之间任务负载迁移的算法,可以启用进程迁移来降低繁忙的处理器的压力。
一般情况下,在应用程序中只需使用缺省的调度器行为。然而,您可能会希望修改这些缺省行为以实现性能的优化。让我们来看一下使用硬亲和性(affinity) 的 3 个原因。
原因 1. 有大量计算要做
基于大量计算的情形通常出现在科学和理论计算中,但是通用领域的计算也可能出现这种情况。一个常见的标志是您发现自己的应用程序要在多处理器的机器上花费大量的计算时间。
原因 2. 提高Cache命中率
在多核运行的机器上,每个CPU都有自己的缓存,缓存着进程使用的信息,而进程可能会被OS调度到其他CPU上,如此一来CPU Cache命中率就低了。当绑定CPU后,程序就会一直在指定的cpu跑,不会由OS调度到其他CPU上,提高CPU Cache命中率。
原因 3. 正在运行时间敏感的、决定性的进程
我们对 CPU 亲和性(affinity)感兴趣的最后一个原因是实时(对时间敏感的)进程。例如,您可能会希望使用硬亲和性(affinity)来指定一个 8 路主机上的某个处理器,而同时允许其他 7 个处理器处理所有普通的系统调度。这种做法确保长时间运行、对时间敏感的应用程序可以得到运行,同时可以允许其他应用程序独占其余的计算资源。
五、如何设置硬亲和性(affinity)?
1.用户态进程与CPU绑定
函数原型:
int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
该函数设置进程为pid的这个进程,让它运行在mask所设定的CPU上.如果pid的值为0,则表示指定的是当前进程,使当前进程运行在mask所设定的那些CPU上。第二个参数cpusetsize是mask所指定的数的长度.通常设定为sizeof(cpu_set_t)。如果当前pid所指定的进程此时没有运行在mask所指定的任意一个CPU上,则该指定的进程会从其它CPU上迁移到mask的指定的一个CPU上运行。
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
该函数获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中。即获得指定pid当前可以运行在哪些CPU上。同样,如果pid的值为0。也表示的是当前进程。
宏定义:
void CPU_ZERO (cpu_set_t *set);
对CPU 集 set 进行初始化,将其设置为空集。
void CPU_SET (int cpu, cpu_set_t *set);
将指定的 cpu 加入 CPU 集 set 中
void CPU_CLR (int cpu, cpu_set_t *set);
将指定的 cpu 从 CPU 集 set 中删除。
int CPU_ISSET (int cpu, const cpu_set_t *set);
如果 cpu 是 CPU 集 set 的一员,这个宏就返回一个非零值(true),否则就返回零(false)。
相关函数:
long sysconf(int name);
参数name取值:
_SC_NPROCESSORS_CONF:查看cpu的个数
_SC_NPROCESSORS_ONLN:查看正在使用的cpu个数
_SC_PAGESIZE:查看缓存内存页面的大小
_SC_PHYS_PAGES:查看内存的总页数
_SC_AVPHYS_PAGES:查看可以利用的总页数
_SC_LOGIN_NAME_MAX:查看最大登录名长度
_SC_HOST_NAME_MAX:查看最大主机名长度
_SC_OPEN_MAX:每个进程运行时打开的文件数目
_SC_CLK_TCK:查看每秒中跑过的运算速率
sysconf(_SC_PAGESIZE) * sysconf(_SC_PHYS_PAGES) :计算内存大小
伪代码:
cpu_set_t mask;
/* 初始化set集,将set设置为空*/
CPU_ZERO(&mask);
/* 依次将0、1号cpu加入到集合*/
CPU_SET(0, &mask);
CPU_SET(1, &mask);
/*将当前进程绑定到cpu */
sched_setaffinity(0, sizeof(mask), &mask);
2.用户态线程与CPU绑定
函数原型
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);
相关函数:
pthread_t pthread_self(void);
功能是获得线程自身的ID。
伪代码:
cpu_set_t mask;
/* 初始化set集,将set设置为空*/
CPU_ZERO(&mask);
/* 依次将0、1号cpu加入到集合*/
CPU_SET(0, &mask);
CPU_SET(1, &mask);
/*将当前线程程绑定到cpu */
pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask);