C/C++线程绑核详解

news/2024/12/2 16:48:52/

        在一些大型的工程或者特殊场景中,我们会听到绑核,绑核分为进程绑核和线程绑核。绑核的最终目的都是为了提高程序和性能或者可靠性。

一:为什么需要绑核

        操作系统发展至今,已经能很好的平衡运行在操作系统上层的应用,兼顾性能和可靠性。一般情况下,在应用程序中只需使用缺省的调度器即可。然而,在某些场景下操作系统默认的调度算法会造成程序的瓶颈,要突破这个瓶颈可能要修改这些缺省行为以实现性能上的突破。以下三个场景比较适合去绑核操作。

1,大量的计算

        基于大量计算的情形通常出现在科学和理论计算中,但是通用领域的计算也可能出现这种情况。一个常见的标志是您发现自己的应用程序要在多处理器的机器上花费大量的计算时间。

 2,测试复杂的应用程序

        测试复杂软件是我们对内核的亲和性(affinity)技术感兴趣的另外一个原因。考虑一个需要进行线性可伸缩性测试的应用程序。有些产品声明可以在 使用更多硬件 时执行得更好。

3,正在运行时间敏感的、决定性的进程 

        我们对 CPU 亲和性(affinity)感兴趣的最后一个原因是实时(对时间敏感的)进程。例如,您可能会希望使用硬亲和性(affinity)来指定一个 8 路主机上的某个处理器,而同时允许其他 7 个处理器处理所有普通的系统调度。这种做法确保长时间运行、对时间敏感的应用程序可以得到运行,同时可以允许其他应用程序独占其余的计算资源。

        在DPDK中,由于需要频繁的去收发包,为了提高收发包的性能,绑核操作在DPDK中非常的常见。DPDK通过把线程绑定到逻辑核的方法来避免跨核任务中的切换开销,减少线程调度的开销,来提高性能。 

二:绑核的基本概念

1,CPU的亲和性

        在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。

        上面提到了软亲和性,那对应的就有硬亲和性。soft affinity和hard affinity。soft affinity仅是一个建议,如果不可避免,调度器还是会把进程调度到其它的CPU上。hard affinity是调度器必须遵守的规则。

2,CPU亲和性掩码

        在绑核的操作中会经常看到mask这个单词,可能初学的不太理解这个mask代表的意义。学过TCP/IP的应该知道mask在ip中就是掩码的意思,利用掩码来划分子网,本质上就是用1和对应位上做&操作。

        在 Linux 内核中,所有的进程都有一个相关的数据结构,称为 task_struct。这个结构非常重要,原因有很多;其中与 亲和性(affinity)相关度最高的是 cpus_allowed 位掩码。这个位掩码由 n 位组成,与系统中的 n 个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。

        如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 CPU 上运行。因此,如果一个进程可以在任何 CPU 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上,这就是 Linux 中进程的缺省状态。

        我们可以查看自己服务器有多少个核心

[ftz@host6 ~]$ cat /proc/cpuinfo |grep processor | wc -l
64
[ftz@host6 ~]$ nproc
64

 3,绑核的代码接口

 以下是绑核的常用宏接口:

//结构体cpu_set_t,定义了CPU核心集合的数据结构
cpu_set_t *set//初始化cpu set集合
void CPU_ZERO (cpu_set_t *set); //单个cpu加到集合中
void CPU_SET (int cpu, cpu_set_t *set);//单个cpu从集中移出
void CPU_CLR (int cpu, cpu_set_t *set); //判断某个cpu是否在cpu集中
int CPU_ISSET (int cpu, const cpu_set_t *set); 

        cpu集可以认为是一个掩码,每个设置的位都对应一个可以合法调度的 cpu,而未设置的位则对应一个不可调度的 CPU。换而言之,线程都被绑定了,只能在那些对应位被设置了的处理器上运行。通常,掩码中的所有位都被置位了,也就是可以在所有的cpu中调度。

 线程绑核用到的函数接口:

long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)

        该函数设置进程为pid的这个进程,让它运行在mask所设定的CPU上.如果pid的值为0,则表示指定的是当前进程,使当前进程运行在mask所设定的那些CPU上.第二个参数cpusetsize是mask所指定的数的长度.通常设定为sizeof(cpu_set_t).如果当前pid所指定的进程此时没有运行在mask所指定的任意一个CPU上,则该指定的进程会从其它CPU上迁移到mask的指定的一个CPU上运行. 

long sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask) 

         该函数获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中.即获得指定pid当前可以运行在哪些CPU上.同样,如果pid的值为0.也表示的是当前进程

三:C语言代码实现线程绑核

 代码实现:起三个线程绑到三个cpu上

#define _GNU_SOURCE  //该宏定义必须有
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <unistd.h>
#include <pthread.h>#define CREATE_THREAD_NUMBER 3
#define JUDGE_IS_SUCCESS(RET) (RET == -1)cpu_set_t g_cpuset;  //定义cpu集合struct thread_info
{pthread_t thread_id;int thread_num;
};void do_thing_busy_cpu()
{int j = 0;int buf[10240];while(j++ < 1024*1024){memset(buf, 0, sizeof(buf));}
}void *threadFunc(void *arg)
{struct thread_info *subThreadInfo = (struct thread_info *)arg;pthread_t subThreadID = subThreadInfo->thread_id;CPU_SET(subThreadInfo->thread_num, &g_cpuset); //将编号为subThreadInfo->thread_num的cpu核加到cpuset集合中if(JUDGE_IS_SUCCESS(sched_setaffinity(getpid(),sizeof(cpu_set_t),&g_cpuset))){printf("ERROR:set CPU affinity fail!\n");}while(1){cpu_set_t getCpuset;CPU_ZERO(&getCpuset);if(JUDGE_IS_SUCCESS(sched_getaffinity(getpid(),sizeof(cpu_set_t),&getCpuset))){printf("ERROR:get CPU affinity fail!\n");}for(int i=0; i<CREATE_THREAD_NUMBER; i++){if(CPU_ISSET(i,&getCpuset)){printf("thread %d is running on cpu: %d\n",subThreadInfo->thread_id,i);do_thing_busy_cpu();}}        }return NULL;
}int main(int argc, char *argv[])
{struct thread_info threadInfo[CREATE_THREAD_NUMBER];int cpuNum = sysconf(_SC_NPROCESSORS_CONF);printf("cpu number:%d\n",cpuNum);CPU_ZERO(&g_cpuset); //初始化cpu集合//创建3个线程for(int i=0; i<CREATE_THREAD_NUMBER; i++){threadInfo[i].thread_num = i;pthread_create(&threadInfo[i].thread_id,NULL,threadFunc,&threadInfo[i]);}for(int i=0; i<CREATE_THREAD_NUMBER; i++){pthread_join(threadInfo[i].thread_id,NULL);}
}

编译:

gcc bindcpu.c -o bindcpu -lpthread -std=c99

运行结果:

cpu number:64
thread 1235138304 is running on cpu: 0
thread 1226745600 is running on cpu: 0
thread 1218352896 is running on cpu: 0
thread 1235138304 is running on cpu: 0
thread 1226745600 is running on cpu: 1
thread 1218352896 is running on cpu: 1
thread 1235138304 is running on cpu: 1
thread 1226745600 is running on cpu: 0
thread 1218352896 is running on cpu: 2
thread 1235138304 is running on cpu: 2
thread 1226745600 is running on cpu: 1
thread 1218352896 is running on cpu: 0
thread 1235138304 is running on cpu: 0
thread 1226745600 is running on cpu: 2
thread 1218352896 is running on cpu: 1
thread 1235138304 is running on cpu: 1
thread 1226745600 is running on cpu: 0
thread 1218352896 is running on cpu: 2
thread 1235138304 is running on cpu: 2
thread 1226745600 is running on cpu: 1
...

可以用top命令查看进程是不是占用了三个cpu

 按1查看cpu核使用详情,由于我用的服务器是共享的大家都在上面用,所以直观的看不出来

 可以通过taskset命令来查看我们程序的CPU亲和度:

[ftz@host6 ~]$ taskset -p 1376833
pid 1376833's current affinity mask: 7

7的二进制就是111,正好符合我们的预期

 除了用代码绑核,也可以用shell命令taskset来设置进程的 CPU 亲和度 (affinity),它可以将一个进程绑定到某个特定的 CPU 核心或一组 CPU 核心。

1,绑定一个进程到单个 CPU 核心:
taskset -c <core_id> <command>
例:将进程绑定到第 1 个 CPU 核心上
taskset -c 0 command2,绑定一个进程到多个 CPU 核心
taskset -c <core_id_list> <command>
例:进程绑定到第 1、2、4 个 CPU 核心上
taskset -c 0,1,3 command3,显示一个进程当前的 CPU 亲和度:
taskset -p <pid>4,修改一个正在运行的进程的 CPU 亲和度:
taskset -cp <core_id_list> <pid>
例:将进程 12345 绑定到第 1、2、4 个 CPU 核心上
taskset -cp 0,1,3 12345

http://www.ppmy.cn/news/188882.html

相关文章

【youcans的深度学习 10】PyTorch入门教程:张量的统计运算与比较运算

欢迎关注『youcans的深度学习』系列&#xff0c;持续更新中… 【youcans的深度学习 01】安装环境之 miniconda 【youcans的深度学习 02】PyTorch CPU版本安装与环境配置 【youcans的深度学习 03】PyTorch CPU版本安装与环境配置 【youcans的深度学习 04】PyTorch入门教程&#…

MongoDB 分片集的基本概念

什么是分片集&#xff1f; 副本集&#xff08;ReplicaSet&#xff09; 用于解决读请求扩展、高可用等问题。但随着业务场景的进一步增长&#xff0c;可能会出现以下问题&#xff1a; 存储容量超出单机磁盘容量&#xff1b;活跃数据集超出单机内存容量&#xff0c;很多读请求需…

笔记本电脑小米画报的壁纸位置

前言 恰好我用小米的笔记本&#xff0c;配着小米画报和windows11使用体验还是很不错的&#xff0c;壁纸的质量不俗&#xff0c;但我想作为图片化作素材的时候&#xff0c;我犯了难&#xff0c;它们这些小米画报使用的壁纸保存到哪里来&#xff1f; 1.自带的小米画报 这个位置…

超清电脑壁纸任你选

网上搜索到的壁纸&#xff0c;多数参差不齐&#xff0c;质量高的数量少&#xff0c;数量多的质量差&#xff01; 专业提供壁纸的网站可以满足我们对壁纸的要求 高图网 分类很多。基本涵盖了众多用户们的日常需求。 图片质量&#xff0c;每一张都非常的高。 尤其是二次元的壁…

数据库期末复习(6)基于哈希和B+树的索引查询

免责声明 练习题没有答案 图片都是自己做的 仅供参考 可扩展哈希表和练习 笔记 数据库--- 索引结构 (2)--可扩展哈希表及增删查_旅僧的博客-CSDN博客 练习 首先默认 局部深度都是1 然后进行插入 之后分裂 按照课件上的操作进行。 线性哈希表

Linux编译器(gcc/g++)调试器gdb项目自动化构建工具(make/Makefile)版本管理git

Linux编译器-gcc/g&&调试器gdb&&项目自动化构建工具-make/Makefile&&版本管理git &#x1f506;gcc/g的使用可执行文件的"生产"过程gcc如何完成预处理编译汇编链接 函数库函数库一般分为静态库和动态库两种静态C/C库的安装 gcc选项gcc选项记…

海信英语AI面试题

题量是三道题&#xff0c;第一题是给你一段话&#xff0c;让你读出来然后录制视频。 后面两道题是有一段问题的音频给你&#xff0c;注意只可以听两遍&#xff0c;一定要听清楚。 后两题有五分钟的答题时间&#xff0c;但是视频最多只能录两分钟时间&#xff0c;可以录两遍&a…

海信85u8e和海信85e7f有什么区别 哪个好详细性能配置对比

海信U8E具备高端时尚的外d观&#xff0c;能够百搭各种居家风格&#xff0c;&#xff0c;自主研发的画质芯片搭配上U超画质引擎&#xff0c;带来绝佳的视觉享受。说起来&#xff0c;这是一款同价位质量最好&#xff0c;非常适合年轻人选择的电视 选海信85u8e还是海信85e7f这些点…