增加 Linux 进程、线程与 CPU 的亲和性(Affinity).
Signed-off-by: rick.chan <chenyang@autoai.com>
This commit is contained in:
parent
64c5296308
commit
3832ea5e2e
|
@ -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/)
|
||||
|
|
|
@ -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++) { /* 将 0、1、2、3 添加到集合中 */
|
||||
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
|
||||
```
|
Loading…
Reference in New Issue