一、多核CPU的结构

服务器的多核结构属于NUMA(Non-Uniform Memory Access,非一致性内存访问)架构。这里有一篇很好的介绍NUMA架构理解

以我们课题组的服务器为例,上面有两个“插槽”,即两个socket(物理概念),逻辑上称为节点node。

每个插槽10个物理核core,每个物理核通过超线程技术可以产生两个逻辑核processor。

因此我们服务器可以有$ 2 \times 10 \times 2=40$个线程 。

CPU亲和的意思是,假设我们产生40个线程,希望每个线程运行在他们各自的node上或者core上,这样可以减少程序运行时的内存搬运。

二、查看服务器配置

cat /proc/cpuinfo #查看所有processor的信息

core.jpg

这是最后一个逻辑核的信息,总共有40个逻辑核,该逻辑核在node1节点的12号cpu上(这里的编号不一定连续)。所有的逻辑核分布如下:

core2.jpg

因此我们设置线程时,可以绑在同一个node上,即绑在逻辑核编号为{0,2,...,18,20,22,...,38}的集合,或{1,3,...,19,21,23,...,39}的集合,让内存不要在Node0和Node1之间发生。

也可以绑在同一个core上,即绑在逻辑核编号为{0,20},{2,22},{4,24},...,或{19,39}的集合,这样不同物理核之间也不会用内存交换,提高运行速度。

可以用numactl程序查看这些核的分布情况,如没有可以安装sudo apt install numactl,但服务器上我们一般没有root权限,还可以这样查看:

cat /sys/devices/system/node/node0/cpulist
cat /sys/devices/system/node/node0/cpumap

三、设置pthread亲和性

还是用求和的例子说明:

#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <stdlib.h>
#include <pthread.h> 
#include <unistd.h>
​
const int S = 100000000;
int* arr;
​
typedef struct{
 int first;
 int last;
 int result;
}MY_ARGS;
​
void* mysum(void* args){
 int i;
 int s=0;
 MY_ARGS* my_args = (MY_ARGS*) args;
 int first = my_args->first;
 int last = my_args->last;
​
 for(i=first;i<last;i++){
 s = s + arr[i];
 }
 my_args -> result = s;
 return NULL;
}
​
int main(){
 int i;
 arr = malloc(sizeof(int) * S);
 for(i=0;i<S;i++){
 if(i%2==0)
 arr[i]=i;
 else
 arr[i]=-i;
 }

 //亲和性的process集合
 int blist[20]={1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39};
 cpu_set_t cpuset_1,cpuset_2;
 CPU_ZERO(&cpuset_1);
 CPU_ZERO(&cpuset_2);
 for(i=0;i<20;i++){
 CPU_SET(blist[i], &cpuset_1);    //cpuset_1设定奇数核
 CPU_SET(blist[i]-1, &cpuset_2);  //cpuset_2设定偶数核
 }
 //亲和性的属性设置
 pthread_attr_t attr1;
 pthread_attr_t attr2;
 pthread_attr_init(&attr1);
 pthread_attr_init(&attr2);
 pthread_attr_setaffinity_np(&attr1, sizeof(cpu_set_t), &cpuset_1);  //attr1 设置奇数核属性
 pthread_attr_setaffinity_np(&attr2, sizeof(cpu_set_t), &cpuset_2);  //attr2 设置偶数核属性
​
 // 产生两个线程
 pthread_t th1;
 pthread_t th2;

 int mid = S/2;
 MY_ARGS args1 = {0,mid,0};
 MY_ARGS args2 = {mid,S,0};
 //设置哪个线程,绑核属性,执行函数,函数参数
 pthread_create(&th1,&attr1,mysum,&args1);  
 pthread_create(&th2,&attr2,mysum,&args2); 
​
 pthread_join(th1,NULL);
 pthread_join(th2,NULL);
​
 printf("sum: %d\n",args1.result + args2.result);
 return 0;
}

这里将两个线程绑分布绑在node0和node1节点,也可以都绑在同一个节点,因为每个节点最多能跑20个线程,问题不大。

更多实例可以参看我的gitee:pthread亲和性

标签: 算法, C++, 亲和性

添加新评论