cpu绑核

pro3399-cpu: 0-3:little(A53)  4-5:big(A72)adb shellsucat /sys/devices/system/cpu/onlineecho 0 >/sys/devices/system/cpu/cpu4/onlinecat /proc/cpuinfo

安卓进程绑定:

安卓进程/线程绑定cpu_android绑定cpu,android绑定cpu-Android代码类资源-CSDN下载

以下内容转自:

【AI移动端算法优化】四,移动端arm cpu优化学习笔记之绑定cpu(cpu affinity)

用过一些移动端推理框架比如ncnn和paddlelite等框架的同学都会发现这些框架提供了运行时功耗级别的设置,其实里面就是用绑核的方式实现的,通过绑定大核实现高功耗模式,绑定小核实现低功耗模式。关于arm大小核的概念可以参考[1]

本文相关实验代码:https://github.com/Ldpe2G/ArmNeonOptimization/tree/master/cpuAffinityExperiments

绑核的概念

简单来说就是把一个进程绑定到一到多个cpu上,也就是让一些进程只运行在可运行的cpu上。比如:让一个进程只运行在某个cpu上或者让一些进程只运行在除了某个cpu以外的cpu上等等。

那绑核有什么作用呢?

首先是可以控制进程运行耗时,比如大核主频高,绑定到大核上运行起来应该会比绑定到小核上运行耗时小。

其次根据文章[2]的说法,可以优化缓存性能。

我个人理解就是如果进程没有绑定在一个cpu上,那么当该进程切换cpu的时候,新cpu 的 cache上并没有之前cpu cache上缓存的数据,就会导致cache miss,然后需要从内存加载数据,然后过一段时间切回去原来cpu之后,可能原来的cache里面的内容也失效了,又导致cache miss,那么这样来回切换就很影响性能。

ncnn & paddlelite绑核实现

下面来看下一些推理框架上的实现:

paddlelite实现代码:

https://github.com/PaddlePaddle/Paddle-Lite/blob/develop/lite/core/device_info.cc#L453

int set_sched_affinity(const std::vector<int>& cpu_ids) {
// #define CPU_SETSIZE 1024
// #define __NCPUBITS  (8 * sizeof (unsigned long))
// typedef struct
// {
//    unsigned long __bits[CPU_SETSIZE / __NCPUBITS];
// } cpu_set_t;// set affinity for thread
#ifdef __GLIBC__pid_t pid = syscall(SYS_gettid);
#elsepid_t pid = gettid();
#endifcpu_set_t mask;CPU_ZERO(&mask);for (int i = 0; i < cpu_ids.size(); ++i) {CPU_SET(cpu_ids[i], &mask);}int syscallret = syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);if (syscallret) {return -1;}return 0;
}

ncnn实现代码:

https://github.com/Tencent/ncnn/blob/ee41ef4a378ef662d24f137d97f7f6a57a5b0eba/src/cpu.cpp#L319

static int set_sched_affinity(size_t thread_affinity_mask)
{// cpu_set_t definition// ref http://stackoverflow.com/questions/16319725/android-set-thread-affinity
#define CPU_SETSIZE 1024
#define __NCPUBITS  (8 * sizeof (unsigned long))
typedef struct
{unsigned long __bits[CPU_SETSIZE / __NCPUBITS];
} cpu_set_t;#define CPU_SET(cpu, cpusetp) \((cpusetp)->__bits[(cpu)/__NCPUBITS] |= (1UL << ((cpu) % __NCPUBITS)))#define CPU_ZERO(cpusetp) \memset((cpusetp), 0, sizeof(cpu_set_t))// set affinity for threadpid_t pid = syscall(SYS_gettid);cpu_set_t mask;CPU_ZERO(&mask);for (int i=0; i<(int)sizeof(size_t) * 8; i++){if (thread_affinity_mask & (1 << i))CPU_SET(i, &mask);}int syscallret = syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);if (syscallret){fprintf(stderr, "syscall error %d\n", syscallret);return -1;}return 0;
}

其实从实现上看两者基本大同小异,下面就以ncnn的实现去分析。

刚开始理解代码的时候,我谷歌了一下sched_setaffinity这个系统调用,搜到了android源码里的声明头文件:

https://android.googlesource.com/platform/bionic.git/+/master/libc/include/sched.h

#ifndef _SCHED_H_
#define _SCHED_H_
#include <bits/timespec.h>
#include <linux/sched.h>
#include <sys/cdefs.h>
__BEGIN_DECLS
......
int sched_getcpu(void);#ifdef __LP64__
#define CPU_SETSIZE 1024
#else
#define CPU_SETSIZE 32
#endif
#define __CPU_BITTYPE  unsigned long int  /* mandated by the kernel  */
#define __CPU_BITS     (8 * sizeof(__CPU_BITTYPE))
#define __CPU_ELT(x)   ((x) / __CPU_BITS)
#define __CPU_MASK(x)  ((__CPU_BITTYPE)1 << ((x) & (__CPU_BITS - 1)))
typedef struct {__CPU_BITTYPE  __bits[ CPU_SETSIZE / __CPU_BITS ];
} cpu_set_t;
int sched_setaffinity(pid_t __pid, size_t __set_size, const cpu_set_t* __set);
#define CPU_ZERO(set)          CPU_ZERO_S(sizeof(cpu_set_t), set)
#define CPU_SET(cpu, set)      CPU_SET_S(cpu, sizeof(cpu_set_t), set)
......
#define CPU_ZERO_S(setsize, set)  __builtin_memset(set, 0, setsize)
#define CPU_SET_S(cpu, setsize, set) \do { \size_t __cpu = (cpu); \if (__cpu < 8 * (setsize)) \(set)->__bits[__CPU_ELT(__cpu)] |= __CPU_MASK(__cpu); \} while (0)
......
#endif /* _SCHED_H_ */

为了可读性,我简化了代码,可以看到ncnn的实现里的cpu_set_t结构体和宏定义基本和源码里的一致。

下面详细分析下ncnn代码实现:

// thread_affinity_mask是一个size_t类型变量,假设是4个字节32bit,
// 则该变量每一比特位对应一个cpu编号,0表示不绑定,1表示绑定
// 比如只绑定cpu0,则该变量的比特位表示:00000000000000000000000000000001
// 比如只绑定cpu0和31,则该变量的比特位表示:10000000000000000000000000000001
// 如果要绑定所有核,则是:11111111111111111111111111111111
// 该变量每个bit位由用户根据需要绑定的cpu编号设定。
static int set_sched_affinity(size_t thread_affinity_mask)
{
#define CPU_SETSIZE 1024
#define __NCPUBITS  (8 * sizeof (unsigned long))
// 这里我机器上sizeof (unsigned long)==4,
// 所以cpu_set_t的__bits数组长度是1024/32=32,
// 也就是32个32bit的变量,因为一个32bit就能表示32个cpu
// 而手机一般也就8个cpu,所以感觉其实一个unsigned long变量就足够了
// 我也做实验验证了,后面实验部分会再详细说明
typedef struct
{unsigned long __bits[CPU_SETSIZE / __NCPUBITS];
} cpu_set_t;// 设置mask变量比特位宏
#define CPU_SET(cpu, cpusetp) \((cpusetp)->__bits[(cpu)/__NCPUBITS] |= (1UL << ((cpu) % __NCPUBITS)))#define CPU_ZERO(cpusetp) \memset((cpusetp), 0, sizeof(cpu_set_t))// set affinity for threadpid_t pid = syscall(SYS_gettid);cpu_set_t mask;// 先把mask清零CPU_ZERO(&mask);// 然后遍历用户传入的thread_affinity_mask变量的每一比特位// 如果某一位为1,则设置cpu_set_t->__bits变量对应位置// 其实从CPU_SET宏的实现也可以看到,尽管__bits数组长度是32,// 但是只会访问到第0个变量,因为 i < 32,// 所以为什么我觉得直接一个unsigned long变量就足够了for (int i=0; i<(int)sizeof(size_t) * 8; i++){if (thread_affinity_mask & (1 << i))CPU_SET(i, &mask);}// 最后调用`__NR_sched_setaffinity`,传入mask,完成绑定int syscallret = syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);

实验&结果分析

看懂ncnn实现代码之后,我自己简单设置了3个对比实验来验证绑核的作用,就是通过人为绑定大小核,来观察代码运行时间的变化。

完整实验代码见:https://github.com/Ldpe2G/ArmNeonOptimization/tree/master/cpuAffinityExperiments

每个实验共同部分都是:

开4个线程,每个线程内调用一次BoxFilter,时间统计是从开启4个线程到4个线程都退出的总耗时。

三个实验的区别部分是:

第一个实验,并不设置绑核; 第二个实验,绑定小核; 第三个实验,绑定大核。

首先给出上面提到的绑定函数实现上的简化(完整代码见github链接),这里可以看到直接用用户传入的mask即可:

static int set_sched_affinity(size_t thread_affinity_mask)
{pid_t pid = syscall(SYS_gettid);int syscallret = syscall(__NR_sched_setaffinity, pid, sizeof(thread_affinity_mask), &thread_affinity_mask);.....return 0;
}

还有这里对于大小核的设置有一点需要注意的是,大小核是一个相对的概念,就是对于一台设备来说,所谓小核就是在这台设备上,相对其他核,频率最低的,大核就是相对频率最高的。

一般推理框架内对于大小核都有判定逻辑,比如ncnn的策略:

static int setup_thread_affinity_masks()
{// 绑定所有核,那就是把mask对应所有cpu编号位设为1// 比如8个核,1左移8位就是 100000000,再减一就是1111111,// 相当于绑定0~7号cpug_thread_affinity_mask_all = (1 << g_cpucount) - 1;// 统计所有cpu中的最大和最小主频int max_freq_khz_min = INT_MAX;int max_freq_khz_max = 0;std::vector<int> cpu_max_freq_khz(g_cpucount);for (int i=0; i<g_cpucount; i++){// get_max_freq_khz函数获取cpu编号对应的主频// 具体实现见ncnn源码,或者github上代码int max_freq_khz = get_max_freq_khz(i);cpu_max_freq_khz[i] = max_freq_khz;if (max_freq_khz > max_freq_khz_max)max_freq_khz_max = max_freq_khz;if (max_freq_khz < max_freq_khz_min)max_freq_khz_min = max_freq_khz;}// 计算主频中值int max_freq_khz_medium = (max_freq_khz_min + max_freq_khz_max) / 2;// 就是如果主频低于中值则算为小核,否则算大核for (int i=0; i<g_cpucount; i++){if (cpu_max_freq_khz[i] < max_freq_khz_medium)g_thread_affinity_mask_little |= (1 << i);elseg_thread_affinity_mask_big |= (1 << i);}return 0;
}

paddlelite策略和ncnn类似,也是计算最大主频和最小主频取中间值,大于中间值的的就是大核,否则就是小核。具体代码见:

https://github.com/PaddlePaddle/Paddle-Lite/blob/develop/lite/core/device_info.cc#L345

不过paddlelite还多了一步,就是对于一些预定义好的SOC,会人为设定其大小核,感兴趣的读者可以去看下完整代码:

https://github.com/PaddlePaddle/Paddle-Lite/blob/develop/lite/core/device_info.cc#L646

下面只列出部分实现代码:

bool DeviceInfo::SetCPUInfoByName() {......else if (dev_name_.find("KIRIN980") != std::string::npos ||dev_name_.find("KIRIN990") !=std::string::npos) {  // Kirin 980, Kirin 990core_num_ = 8;core_ids_ = {0, 1, 2, 3, 4, 5, 6, 7};big_core_ids_ = {4, 5, 6, 7};little_core_ids_ = {0, 1, 2, 3};cluster_ids_ = {1, 1, 1, 1, 0, 0, 0, 0};SetArchInfo(2, kA76, kA55);SetCacheInfo(0, 2, 64 * 1024, 32 * 1024);SetCacheInfo(1, 2, 512 * 1024, 128 * 1024);SetCacheInfo(2, 1, 4096 * 1024);SetFP16Info(1, 1);SetDotInfo(1, 1);return true;.....

然后回来看下实验代码:

// 线程函数实现,就是打印线程当前运行所在的cpu,然后执行boxfilter
// 注释掉的代码,读者感兴趣去实验的时候可以打开看看,
// 如果不是人为绑定到具体一个核上运行的话,
// 有几率会看到线程在睡眠前后切换了运行的cpu
std::mutex g_display_mutex;
void threadFun() {size_t cpu;auto syscallret = syscall(__NR_getcpu, &cpu, NULL, NULL);std::stringstream ss;ss << std::this_thread::get_id();g_display_mutex.lock();printf("thread %s, running on cpu: %d\n", ss.str().c_str(), cpu);g_display_mutex.unlock();// std::this_thread::sleep_for (std::chrono::microseconds(100));Boxfilter(7, 500, 500);// syscallret = syscall(__NR_getcpu, &cpu, NULL, NULL);// g_display_mutex.lock();// printf("thread %s, running on cpu: %d\n", ss.str().c_str(), cpu);// g_display_mutex.unlock();
}

我的实验机器是华为P30(Kirin 980),先看下核数和cpu的主频信息:

before sort
cpu_0:1805000, cpu_1:1805000, cpu_2:1805000, cpu_3:1805000,
cpu_4:1920000, cpu_5:1920000, cpu_6:2600000, cpu_7:2600000,
after sort
cpu_6:2600000, cpu_7:2600000, cpu_4:1920000, cpu_5:1920000,
cpu_2:1805000, cpu_3:1805000, cpu_0:1805000, cpu_1:1805000,

可以看到和paddlelite里面设置一致,cpu 0~3是小核,4~7是大核,当然6~7比4~5主频又要高一些。

给出实验代码,方便读者理解,完整代码见github:

// bind all coresprintf("bind all cores ex:\n");auto start = std::chrono::steady_clock::now(), stop = start;for (int i = 0; i < threadNum; ++i) {std::thread t(threadFun);threads.push_back(std::move(t));}for (int i = 0; i < threadNum; ++i) {threads[i].join();}stop = std::chrono::steady_clock::now();auto time = (0.000001 * std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count());printf("bind all core time: %f\n\n", time);threads.clear();// bind little coresprintf("bind little cores ex:\n");size_t mask2 = 0;for (int i = 0; i < g_cpucount; ++i) {if (cpu_max_freq_khz[i] == max_freq_khz_min) {mask2 |= (1 << cpu_idx[i]);printf("bind cpu: %d, ", cpu_idx[i]);}}printf("\n");int ret2 = set_sched_affinity(mask2);start = std::chrono::steady_clock::now();for (int i = 0; i < threadNum; ++i) {std::thread t(threadFun);threads.push_back(std::move(t));}for (int i = 0; i < threadNum; ++i) {threads[i].join();}stop = std::chrono::steady_clock::now();time = (0.000001 * std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count());printf("bind little core time: %f\n\n", time);threads.clear();// bind big coresprintf("bind big cores ex:\n");size_t mask = 0;for (int i = 0; i < g_cpucount; ++i) {if (cpu_max_freq_khz[i] >= max_freq_khz_medium) {mask |= (1 << cpu_idx[i]);printf("bind cpu: %d, ", cpu_idx[i]);}}printf("\n");int ret = set_sched_affinity(mask);start = std::chrono::steady_clock::now();for (int i = 0; i < threadNum; ++i) {std::thread t(threadFun);threads.push_back(std::move(t));}for (int i = 0; i < threadNum; ++i) {threads[i].join();}stop = std::chrono::steady_clock::now();time = (0.000001 * std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count());printf("bind big core time: %f\n", time);printf("\n");

看下实验结果:

#########################
iteration 1bind all cores ex:
thread -360738256, running on cpu: 3
thread -358632912, running on cpu: 7
thread -357580240, running on cpu: 5
thread -359685584, running on cpu: 7
bind all core time: 76.996875bind little cores ex:
bind cpu: 2, bind cpu: 3, bind cpu: 0, bind cpu: 1,
thread -357580240, running on cpu: 2
thread -358632912, running on cpu: 3
thread -360738256, running on cpu: 1
thread -359685584, running on cpu: 0
bind little core time: 368.479166bind big cores ex:
bind cpu: 6, bind cpu: 7,
thread -359685584, running on cpu: 4
thread -358632912, running on cpu: 5
thread -360738256, running on cpu: 5
thread -357580240, running on cpu: 4
bind big core time: 99.261979#########################
iteration 2bind all cores ex:
thread -220220880, running on cpu: 1
thread -222326224, running on cpu: 0
thread -221273552, running on cpu: 4
thread -219168208, running on cpu: 5
bind all core time: 257.230729bind little cores ex:
bind cpu: 2, bind cpu: 3, bind cpu: 0, bind cpu: 1,
thread -219168208, running on cpu: 1
thread -220220880, running on cpu: 0
thread -221273552, running on cpu: 2
thread -222326224, running on cpu: 3
bind little core time: 269.061458bind big cores ex:
bind cpu: 6, bind cpu: 7,
thread -222326224, running on cpu: 4
thread -220220880, running on cpu: 5
thread -219168208, running on cpu: 4
thread -221273552, running on cpu: 5
bind big core time: 100.983854

这里只列出了2次实验结果,在脚本我是设定了跑10次,这个读者实验的时候可以自己设置。

根据实验结果可以看到,对比绑定大核和小核,确实绑定大核上运行会比绑定小核运行速度要更快,不过这里大核我是显式绑定6和7,但是多数情况下会失败,绑到了4和5,不知道是不是用户自己绑定有什么限制,如果系统调度就可以跑到6和7。

然后看到迭代1和2的不绑定核(相当于绑定所有核)实验结果,如果刚好这次4个线程多数都跑在了大核上那么速度就会快否则就可能会慢。

3399 cpu绑核相关推荐

  1. 【linux 绑核】CPU 绑核

    前言 以下介绍两个用于CPU绑核命令 taskset 适用于已经在运行的程序 numactl 适用于准备运行的程序 目录 1. 命令 taskset 1.1. 查看进程绑核状态 1.2. 指定PID绑 ...

  2. android cpu绑核

    参考链接: https://mp.weixin.qq.com/s?__biz=MzA4MjY4NTk0NQ==&mid=2247485852&idx=1&sn=5c5f0b3c ...

  3. Linux下cpu和绑核

    基本概念 cpu个数 是指物理上cpu的个数. cpu核心数是指物理上,也就是硬件上存在着几个核心.比如,双核就是包括2个相对独立的CPU核心单元组,四核就包含4个相对独立的CPU核心单元组. cpu ...

  4. 【CPU线程和进程绑核】

    CPU线程和进程绑核 前言 一.进程绑核C语言实现 二.线程绑核C语言实现 三. shell命令绑核 前言 提示:这里可以添加本文要记录的大概内容: CPU绑定指的是在多核CPU的系统中将进程或线程绑 ...

  5. java如何绑核_pod绑核规则

    pod绑核是按照container来绑定的,如果一个pod有多个container对于不同的container会区别对待. // kubernetes/pkg/kubelet/cm/cpumanage ...

  6. 绑核原理linux,DPDK性能影响因素之绑核原理

    原标题:DPDK性能影响因素之绑核原理 背景 DPDK的本质任务就是提升服务器对网络包的处理能力.在性能的提升上,有很多要素,诸如轮询,用户态驱动,亲和性与独占,降低访存开销,软件调优,利用IA新硬件 ...

  7. linux下的绑核命令,Linux下的绑核命令——taskset

    什么是绑核 所谓绑核,其实就是设定某个进程/线程与某个CPU核的亲和力(affinity).设定以后,Linux调度器就会让这个进程/线程只在所绑定的核上面去运行.但并不是说该进程/线程就独占这个CP ...

  8. Intel Optane P4800X评测(3):Windows绑核优化篇

    据了解,使用3D XPoint Memory的Optane P4800X在国内已经开始少量供货,除了一些测试过的人之外,已经开始有采购的用户了.有朋友问我,这个卡在测试中有没有需要注意/调优的地方,以 ...

  9. C/C++线程绑核详解

    在一些大型的工程或者特殊场景中,我们会听到绑核,绑核分为进程绑核和线程绑核.绑核的最终目的都是为了提高程序和性能或者可靠性. 一:为什么需要绑核 操作系统发展至今,已经能很好的平衡运行在操作系统上层的 ...

最新文章

  1. centos7.0 lamp mysql_CentOS7 yum安装LNMP以及LAMP
  2. 按键精灵上传账号到服务器_百度网盘超级会员账号登录器
  3. Spring Security 入门(1-9)国际化的使用
  4. php: eclipse 编辑 php
  5. 工作的准备:atoi,itoa,strcpy,memcpy,strcmp,二分查找,strcat
  6. 这家简历大数据公司被“一锅端” 或因私自抓取用户简历:曾获李开复投资
  7. PHP,mysql,Linux,CI框架学习总结
  8. SQL SERVER 2012 执行计划走嵌套循环导致性能问题的案例
  9. Vue:使用vue-json-excel导出数据到excel
  10. 深度学习CNN系列笔记
  11. matlab图像光照效果模拟
  12. 软件著作权在开发完成时就自动享有了还有必要申请软件著作权登记么?
  13. 电脑提示文件或目录损坏且无法读取
  14. Bootstrap学习心得
  15. RTP协议学习大总结从原理到代码
  16. C# 什么是Asp.net Core?和 .net core有什么区别?
  17. 三步快速远程桌面控制,开启远程办公
  18. 王厚祥谈《古诗四帖》基本笔画的书写方法
  19. Bootstrap-表格合并单元格
  20. python利用有道翻译做一个实时翻译软件

热门文章

  1. Win7 64位的SSDTHOOK(2)---64位SSDT hook的实现
  2. FSD HOOK与SSDT HOOK恢复简单思路
  3. 窗口截图(可指定HWND窗口句柄)
  4. MFC文件操作大全,打开,保存,复制,删除,查找等
  5. 结合typedef更为直观的应用函数指针
  6. Eclipse Theme
  7. TCP/IP详解--学习笔记(4)-ICMP协议,ping和Traceroute
  8. Ubuntu 11.10中用xen-tools安装虚拟机(UbuntuCentOS)
  9. ae中心点重置工具_不懂这些知识,你的AE白学了!
  10. 15g1和g2和g3区别大吗_河南成人高考和普通高考的区别有哪些?成人高考难度会越来越大吗?...