文章目录
参考链接:
相关概念及工具
CPU亲和性
CPU亲和性(affinity) 就是进程要在某个给定的CPU上尽量长时间的运行而不被迁移到其他处理器的倾向性。
linux内核进程调度器天生具有软CPU亲和性(affinity)的特性,着意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。
Linux内核还包含一些机制,它让开发人员可以编程实现硬CPU亲和性(affinity)。着意味着应用程序可以显示的指定进程在那个(或那些)处理器上运行。
查看cpu有几个核
命令查看
$ cat /proc/cpuinfo |grep processor | wc -l
# 或
$ nproc
# 使用cat /proc/cpuinfo可以更详细的查看哪些可以用,哪些不可以用
# 如可以先禁用一个cpu1
$ echo 0 > /sys/devices/system/cpu/cpu1/online
$ cat /proc/cpuinfo
# 再解除禁用,再对比cat /proc/cpuinfo的输出
$ echo 1 > /sys/devices/system/cpu/cpu1/online
$ cat /proc/cpuinfo
# 输出已省略,可自行实验。可以看到,禁用的时候,是没有processor1的
疑问:cat /proc/cpuinfo |grep processor | wc -l 和 nproc 查看的核数不一样 :
用cat /proc/cpuinfo |grep processor | wc -l 查看的是8个核
用nproc 产看的是6个核
代码查看
#include <unistd.h>
int sysconf(_SC_NPROCESSORS_CONF);/* 返回系统可以使用的核数,但是其值会包括系统中禁用的核的数目,因此该值并不代表当前系统中可用的核数 */
int sysconf(_SC_NPROCESSORS_ONLN);/* 返回值真正的代表了系统当前可用的核数 */
/* 以下两个函数与上述类似 */
#include <sys/sysinfo.h>
int get_nprocs_conf (void);/* 可用核数 */
int get_nprocs (void);/* 真正的反映了当前可用核数,比如你禁用的就不显示 */
代码讲解
cpu集(cpu_set_t)
cpu_set_t的使用
cpu_set_t用来描述CPU的集合,被sched_setaffinity等类似的函数使用。
非动态分配cpu_set_t
常用接口声明(在glibc中用本质为宏定义):
void CPU_ZERO(cpu_set_t *set);
void CPU_SET(int cpu, cpu_set_t *set);
void CPU_CLR(int cpu, cpu_set_t *set);
int CPU_ISSET(int cpu, cpu_set_t *set);
int CPU_COUNT(cpu_set_t *set);
void CPU_AND(cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
void CPU_OR(cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
void CPU_XOR(cpu_set_t *destset,
cpu_set_t *srcset1, cpu_set_t *srcset2);
int CPU_EQUAL(cpu_set_t *set1, cpu_set_t *set2);
用法举例:
cpu_set_t set1, set2;
CPU_ZERO(&set1); //清空集合,即set1里不包含任何CPU,本质为所有bit清零
CPU_ZERO(&set2); //清空集合,即set2里不包含任何CPU,本质为所有bit清零
CPU_SET(0, &set1); //将cpu0添加到集合set1中,本质为对应bit置1
CPU_SET(1, &set2); //将cpu1添加到集合set2中,本质为对应bit置1
CPU_CLR(0, &set1); //将cpu0从集合set1中移除,本质为对应bit清零
int ret = CPU_ISSET(1, &set2); //判断cpu1是否在集合set2中,在返回非零,不在返回0
int cnt = CPU_COUNT(&set2); //返回集合set2中的CPU的个数
cpu_set_t result;
CPU_AND(&result, &set1, &set2); //set1和set2的所有bit按位与,结果存入result
CPU_OR(); //按位或
CPU_XOR(); //按位异或
ret = CPU_EQUAL(&set1, &set2); //集合set1和集合set2相等的话,ret为非零,不相等,ret为0
动态分配cpu_set_t
刚开始接触cpu_set_t时,对_S系列接口有疑问,不明白它存在的意义,明明自己malloc一个cpu_set_t就可以,然后使用各种非_S对其操作,为什么非要有_S系列接口呢?具体原因见
glibc源码粗讲解:绑定任务到特定CPU
绑定任务到指定CPU
CPU亲和性只是一种倾向性,当绑定的CPU不存在或者存在但是被禁用了,任务会在其他的CPU上执行。
设置任务亲和性的接口有:
- sched_setaffinity:修改指定pid_t的任务的亲和性
- pthread_setaffinity_np:gnu接口,修改指定pthrad_t的任务的亲和性。
- pthread_attr_setaffinity_np:gnu接口。创建线程前,通过线程属性结构体控制新线程的亲和性。
注:np的意思为不可移植,即在非gnu的系统上,应该是没有这个接口的。
pthread_setaffinity_np
设置线程亲和性,将线程绑定到指定CPU核
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,const cpu_set_t *cpuset);
thread:线程id
cpusetsize:集合内存大小
pthread_getaffinity_np
获取指定线程的CPU集合
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);
thread:线程id
cpusetsize:集合内存大小
cpuset:CPU核的集合
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
void *myfun(void *arg)
{
cpu_set_t mask;
cpu_set_t get;
char buf[256];
int i;
int j;
int num = sysconf(_SC_NPROCESSORS_CONF); // 在运行时获取配置信息,获取CPU核个数
CPU_ZERO(&mask); // 初始化,设为空
printf("system has %d processor(s)\n", num);
for(i = 0; i < num; i++){
CPU_SET(i, &mask); // 将某个cpu加入cpu集中
}
// 将线程绑定到cpu集中的各个CPU核,这样做不太好,会导致线程在各CPU核中切换,导致性能下降
// 本次只为测试
if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0) {
fprintf(stderr, "set thread affinity failed\n");
}
CPU_ZERO(&get);
if (pthread_getaffinity_np(pthread_self(), sizeof(get), &get) < 0) {
fprintf(stderr, "get thread affinity failed\n");
}
for (i = 0; i < num; i++) {
if (CPU_ISSET(i, &get)) {
printf("thread %ld is running in processor %d\n", pthread_self(), i);
}
j = 0;
while (j++ < 100000000){
memset(buf, 0, sizeof(buf));
}
}
pthread_exit(NULL);
}
int main(){
int num = sysconf(_SC_NPROCESSORS_CONF);
printf("CPU核数:%d \n", num);
pthread_t tid; //创建一个线程ID
if (pthread_create(&tid, NULL, myfun, NULL) != 0) { //myfun是线程的入口函数
fprintf(stderr, "thread create failed\n");
return -1;
}
pthread_join(tid, NULL); //回收线程资源
return 0;
}
编译:
g++ -o cpu CPU_Bind.cpp -lpthread
运行:
./cpu
结果:
CPU核数:5
system has 5 processor(s)
thread 140197435995904 is running in processor 0
thread 140197435995904 is running in processor 1
thread 140197435995904 is running in processor 2
thread 140197435995904 is running in processor 3
thread 140197435995904 is running in processor 4
sched_setaffinity
如果考虑可移植性的话,推荐使用sched_setaffinity()函数将任务绑定到特定CPU执行。
将当前的pid绑定到4,5,6,7核上(大核核超大核)
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
int main(){
cpu_set_t cpumask;
CPU_ZERO(&cpumask);
CPU_SET(4, &cpumask);
CPU_SET(5, &cpumask);
CPU_SET(6, &cpumask);
CPU_SET(7, &cpumask);
printf("pid = %d\n",getpid());
sched_setaffinity(getpid(), sizeof(cpumask), &cpumask);
while(1)
{
sleep(1);
}
return 0;
}
编译:
gcc -o bind_cpu cpu.c -pthread
运行:
./bind_cpu
新开一个终端,运行ps -eo pid,args,psr | grep bind_cpu可以查看bind_cpu跑在哪个核上。