当一个进程在一个多处理器系统上被重新调度时无需在上一次执行的CPU上运行。之所以会在另一个 CPU 上运行的原因是原来的CPU处于忙碌状态。
进程切换CPU时对性能会有一定的影响:如果在原来的 CPU 的高速缓冲器中存在进程的数据,那么为了将进程的一行数据(高速缓冲器中的一行与虚拟内存管理系统中的一页是类似的。它是 CPU 高速缓冲器和内存之间传输数据的单位)加载进新 CPU 的高速缓冲器中,首先必须使这行数据失效(即在没被修改的情况下丢弃数据,在被修改的情况下将数据写入内存)。(为防止高速缓冲器不一致,多处理器架构在某个时刻只允许数据被存放在一个CPU的高速缓冲器中)这个使数据失效的过程会消耗时间。由于存在这个性能影响,Linux(2.6)内核尝试了给进程保证软CPU亲和力---在条件允许的情况下进程重新被调度到原来的CPU 上运行。
有时候需要为进程设置硬CPU亲和力,这样就能显式地将其限制在可用CPU中的一个或一组CPU上运行。之所以需要这样做,原因如下。
- 可以避免由使高速缓冲器中的数据失效所带来的性能影响。
- 如果多个线程(或进程)访问同样的数据,那么当将它们限制在同样的CPU上的话可能会带来性能提升,因为它们无需竞争数据并且也不存在由此而产生的高速缓冲器未命中。
- 对于时间关键的应用程序来讲,可能需要为此应用程序预留一个或更多 CPU,而将系统中大多数进程限制在其他CPU上。
在linux主机下执行lscpu命令查看CPU信息,可以得到如下结果:
由上图可知我的Linux系统的CPU信息。那么在进行多进程(多线程)编程的时候,如何为某个进程绑定特定的CPU核心尼?答案就是借助 sched_setaffinity 函数。
sched_setaffinity 函数简介
首先系统man查询一下,得到所需头文件和函数原型:
如果函数调用成功,返回 0,失败的话,返回 -1,错误码放入
errno
。这个函数可以决定线程在指定的cpu中运行。在多进程系统中,适当的为线程指定cpu可以提升效率。sched_setaffinity 函数通过 cpu_set_t 结构体数据类型的掩码(mask)指定 cpu,掩码的操作可以通过一些宏定义实现,比如 CPU_SET等。操作哪一个线程则通过参数一pid指定,如果pid==0,那么为当前正在调用sched_setaffinity 函数的线程指定 cpu。sched_setaffinity 函数指定的 cpu,通过 fork分出的子进程也继承之。
对cpu_set_t 结构体的操作宏定义可以通过man CPU_SET查询:
这些宏操作 set 指向的 CPU 集合:
- CPU_ZERO() 将set初始化为空。
- CPU_SET() 将 CPU cpu添加到set中。
- CPU_CLR() 从set中删除CPU cpu。
- CPU_ISSET()在 CPU cpu 是 set 的一个成员时返回 true。
实例demo如下:
#define _GNU_SOURCE #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char *argv[]) { cpu_set_t set; int parentCPU, childCPU; int nloops, j; if (argc != 4) { printf("Usage: %s parent-cpu child-cpu num-loops\n", argv[0]); return -1; } parentCPU = atoi(argv[1]); childCPU = atoi(argv[2]); nloops = atoi(argv[3]); CPU_ZERO(&set); switch (fork()) { case -1: /* Error */ perror("fork"); case 0: /* Child */ CPU_SET(childCPU, &set); if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) perror("child process sched_setaffinity"); for (j = 0; j < nloops; j++) getppid(); exit(EXIT_SUCCESS); default: /* Parent */ CPU_SET(parentCPU, &set); if (sched_setaffinity(getpid(), sizeof(set), &set) == -1) perror("father process sched_setaffinity"); for (j = 0; j < nloops; j++) getppid(); wait(NULL); /* Wait for child to terminate */ exit(EXIT_SUCCESS); } }
我们使用 linux 的time命令来测试程序运行所需要的时间,测试分为 3 种情况:
- 父子进程使用同一个 cpu
- 父子进程使用同一个 cpu 的不同核心
- 父子进程使用不同的 cpu
结果如下:
每个 core id 均代表一个唯一的处理器内核,所有带有相同 core id 的逻辑处理器均位于同一个处理器内核上。也就是说:processor 0 和 2属于同一个CPU的不同核心,processor1和3属于同一个CPU的不同核心。
real time 是指实际消耗的时间;user time 是指程序运行在用户态消耗的时间;sys time 是指程序运行在内核态消耗的时间
在父子进程使用同一个 cpu 时,real time 近似等于 user time 与 sys time 之和。
当为父子进程指定不同的 cpu 时,real time 小于 user time 与 sys time 之和了,这是因为 user time 和 sys time统计的是总时间,这里父子进程共使用了两个 cpu,因此总的消耗时间被分摊了,此时可以看出 real time 近似等于 user time 与 sys time 之和的一半。同时可以看出当父子进程使用不同CPU的效率略高于使用同一CPU的不同核心处理,但相差不大。