增加 Linux 进程、线程与 CPU 的亲和性(Affinity).

Signed-off-by: rick.chan <chenyang@autoai.com>
This commit is contained in:
rick.chan 2021-06-07 16:27:06 +08:00
parent 64c5296308
commit 3832ea5e2e
2 changed files with 264 additions and 0 deletions

View File

@ -76,6 +76,7 @@
* [Model Zoo](https://modelzoo.co/)
* [Paddle](https://www.paddlepaddle.org.cn/)
* [Paddle AI Studio](https://aistudio.baidu.com/aistudio)
* [Pascal VOC Dataset Mirror](https://pjreddie.com/projects/pascal-voc-dataset-mirror/)
* [Python 机器学习基础教程](https://www.92python.com/ml/)
* [Pandas: 强大的 Python 数据分析支持库](https://www.pypandas.cn/docs/)
* [Mathematics for Machine Learning](https://mml-book.github.io/)

View File

@ -0,0 +1,263 @@
# [Linux 进程、线程与 CPU 的亲和性Affinity](https://www.cnblogs.com/wenqiang/p/6049978.html)
最近的工作中对性能的要求比较高,下面简单做一下总结:
## 1.什么是 CPU 亲和性Affinity
CPU 的亲和性, 就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,也称为 CPU 关联性;再简单的点的描述就将制定的进程或线程绑定到相应的 CPU 上;在多核运行的机器上,每个 CPU 本身自己会有缓存,缓存着进程使用的信息,而进程可能会被 OS 调度到其他 CPU 上如此CPU cache 命中率就低了,当绑定 CPU 后,程序就会一直在指定的 CPU 跑,不会由操作系统调度到其他 CPU 上,性能有一定的提高。
* 软亲和性affinity: 就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性affinity 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。
* 硬亲和性affinity简单来说就是利用 linux 内核提供给用户的 API强行将进程或者线程绑定到某一个指定的 CPU 核运行。
** 解释 **:在 linux 内核中,所有的进程都有一个相关的数据结构,称为 task_struct。这个结构非常重要原因有很多其中与 亲和性affinity相关度最高的是 cpus_allowed 位掩码。这个位掩码由 n 位组成,与系统中的 n 个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。 如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 CPU 上运行。因此,如果一个进程可以在任何 CPU 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上这就是 Linux 中进程的缺省状态这部分内容在这个博客中有提到一点http://www.cnblogs.com/wenqiang/p/4802619.html
cpus_allowed 用于控制进程可以在哪里处理器上运行
* sched_set_affinity() (用来修改位掩码)
* sched_get_affinity() (用来查看当前的位掩码)
## 2.进程与 CPU 的绑定
sched_setaffinity 可以将某个进程绑定到一个特定的 CPU。你比操作系统更了解自己的程序为了避免调度器愚蠢的调度你的程序或是为了在多线程程序中避免缓存失效造成的开销你可能会希望这样做。
在进行进程与 CPU 的绑定前,我们先了解编写程序需要准备的知识点
```cpp
SCHED_SETAFFINITY(2) Linux Programmer's Manual SCHED_SETAFFINITY(2)
NAME
sched_setaffinity, sched_getaffinity - set and get a process's CPU affinity mask
SYNOPSIS
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
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.也表示的是当前进程 */
RETURN VALUE
On success, sched_setaffinity() and sched_getaffinity() return 0. On error, -1 is returned, and errno is set appropriately.
```
设置 CPU Affinity 还需要用到一下宏函数
```cpp
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。*/
```
下面下一个具体的例子:将当前进程绑定到 0、1、2、3 号 CPU 上
```cpp
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
/* sysconf( _SC_NPROCESSORS_CONF ) 查看 CPU 的个数;打印用%ld 长整。
* sysconf( _SC_NPROCESSORS_ONLN ) 查看在使用的 CPU 个数;打印用%ld 长整 */
int main(int argc, char **argv)
{
int cpus = 0;
int i = 0;
cpu_set_t mask;
cpu_set_t get;
cpus = sysconf(_SC_NPROCESSORS_CONF);
printf("cpus: %d\n", cpus);
CPU_ZERO(&mask); /* 初始化 set 集,将 set 置为空 */
CPU_SET(0, &mask); /* 依次将 0、1、2、3 号 CPU 加入到集合,前提是你的机器是多核处理器 */
CPU_SET(1, &mask);
CPU_SET(2, &mask);
CPU_SET(3, &mask);
/* 设置 CPU 亲和性affinity*/
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
printf("Set CPU affinity failue, ERROR:%s\n", strerror(errno));
return -1;
}
usleep(1000); /* 让当前的设置有足够时间生效 */
/* 查看当前进程的 CPU 亲和性 */
CPU_ZERO(&get);
if (sched_getaffinity(0, sizeof(get), &get) == -1) {
printf("get CPU affinity failue, ERROR:%s\n", strerror(errno));
return -1;
}
/* 查看运行在当前进程的 CPU*/
for(i = 0; i < cpus; i++) {
if (CPU_ISSET(i, &get)) { /* 查看 CPU i 是否在 get 集合当中 */
printf("this process %d of running processor: %d\n", getpid(), i);
}
}
sleep(3); // 让程序停在这儿,方便 top 命令查看
return 0;
}
```
运行结果如下:
```bash
[root@localhost test]# ./test
cpus: 24
this process 2848 of running processor: 0
this process 2848 of running processor: 1
this process 2848 of running processor: 2
this process 2848 of running processor: 3
```
上面代码当中用到了 syscall 这个函数,顺便也在这里做一下说明:
syscall 是执行一个系统调用,根据指定的参数 number 和所有系统调用的接口来确定调用哪个系统调用,用于用户空间跟内核之间的数据交换
下面是 syscall 函数原型及一些常用的 number
```cpp
//syscall - indirect system call
SYNOPSIS
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h> /* For SYS_xxx definitions */
int syscall(int number, ...);
/* sysconf( _SC_PAGESIZE ); 此宏查看缓存内存页面的大小;打印用%ld 长整型。
sysconf( _SC_PHYS_PAGES ) 此宏查看内存的总页数;打印用%ld 长整型。
sysconf( _SC_AVPHYS_PAGES ) 此宏查看可以利用的总页数;打印用%ld 长整型。
sysconf( _SC_NPROCESSORS_CONF ) 查看 CPU 的个数;打印用%ld 长整。
sysconf( _SC_NPROCESSORS_ONLN ) 查看在使用的 CPU 个数;打印用%ld 长整。
(long long)sysconf(_SC_PAGESIZE) * (long long)sysconf(_SC_PHYS_PAGES) 计算内存大小。
sysconf( _SC_LOGIN_NAME_MAX ) 查看最大登录名长度;打印用%ld 长整。
sysconf( _SC_HOST_NAME_MAX ) 查看最大主机长度;打印用%ld 长整。
sysconf( _SC_OPEN_MAX ) 每个进程运行时打开的文件数目;打印用%ld 长整。
sysconf(_SC_CLK_TCK) 查看每秒中跑过的运算速率;打印用%ld 长整。*/
```
## 3.线程与 CPU 的绑定
线程于进程的绑定方法大体一致,需要注意的是线程绑定于进程的区别是所用函数不一样
线程绑定用到下面两个函数,跟进程类似就不做详细说明,下面直接贴出函数原型:
```cpp
NAME
pthread_setaffinity_np, pthread_getaffinity_np - set/get CPU affinity of a thread
SYNOPSIS
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <pthread.h>
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);
Compile and link with -pthread.
DESCRIPTION
The pthread_setaffinity_np() function sets the CPU affinity mask of the thread thread to the CPU set pointed to by cpuset. If the call is successful, and the thread is not
currently running on one of the CPUs in cpuset, then it is migrated to one of those CPUs.
The pthread_getaffinity_np() function returns the CPU affinity mask of the thread thread in the buffer pointed to by cpuset.
For more details on CPU affinity masks, see sched_setaffinity(2). For a description of a set of macros that can be used to manipulate and inspect CPU sets, see CPU_SET(3).
The argument cpusetsize is the length (in bytes) of the buffer pointed to by cpuset. Typically, this argument would be specified as sizeo(cpu_set_t). (It may be some other
value, if using the macros described in CPU_SET(3) for dynamically allocating a CPU set.)
RETURN VALUE
On success, these functions return 0; on error, they return a nonzero error number
```
下面同样是个具体的例子:将当前线程绑定到 0、1、2、3 号 CPU 上
```cpp
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
void *testfunc(void *arg)
{
int i, cpus = 0;
cpu_set_t mask;
cpu_set_t get;
cpus = sysconf(_SC_NPROCESSORS_CONF);
printf("this system has %d processor(s)\n", cpus);
CPU_ZERO(&mask);
for (i = 0; i < 4; i++) { /* 0123 添加到集合中 */
CPU_SET(i, &mask);
}
/* 设置 CPU 亲和性 (affinity)*/
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0) {
fprintf(stderr, "set thread affinity failed\n");
}
/* 查看 CPU 亲和性 (affinity)*/
CPU_ZERO(&get);
if (pthread_getaffinity_np(pthread_self(), sizeof(get), &get) < 0) {
fprintf(stderr, "get thread affinity failed\n");
}
/* 查看当前线程所运行的所有 CPU*/
for (i = 0; i < cpus; i++) {
if (CPU_ISSET(i, &get)) {
printf("this thread %d is running in processor %d\n", (int)pthread_self(), i);
}
}
sleep(3); // 查看
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, (void *)testfunc, NULL) != 0) {
fprintf(stderr, "thread create failed\n");
return -1;
}
pthread_join(tid, NULL);
return 0;
}
```
运行结果如下:
```bash
[root@localhost thread]# ./test
this system has 24 processor(s)
this thread 2812323584 is running in processor 0
this thread 2812323584 is running in processor 1
this thread 2812323584 is running in processor 2
this thread 2812323584 is running in processor 3
```