1、MIPS、DMIPS
1.1 基本概念
原文链接:CPU性能测试分析MIPS、DMIPS-CSDN博客
MIPS: million instruction per second,表示每秒多少百万条指令,如 10MIPS ,表示每秒一千万条指令。
MIPS/MHz : 表示 CPU 在每 1MHz 的运行速度下可以执行多少个MIPS ,如 10MIPS/MHz,表示如果 CPU 运行在 1MHz 的频率下,每秒可执行一千万条指令,如果 CPU 运行在 5MHz 的频率下,每秒可执行五千万条指令。
DMIPS: Dhrystone MIPS,并非字面上每秒百万条指令的意思。它是一个测量CPU运行一个叫Dhrystone(整数运算)的测试程序时表现出来的相对性能高低的一个单位(很多场合人们也习惯用MIPS作为这个性能指标的单位)。
Dhrystone: 是于1984年由Reinhold P. Weicker设计的一套综合的基准程序,该程序用来测试CPU(整数)计算性能。其名“Dhrystone”是与另一算法“Whetsone”区分而设计的名字。与Whetsone不同,Dhrystone并不包括浮点运算,其输出结果为每秒钟运行Dhrystone的次数,即每秒钟迭代主循环的次数。
Dhrystone的重要性在于其能作为处理器整数计算性能的指标。很多现代的编译器应用了静态代码分析技术,会将对输出没有影响的代码忽略,这会使很多基准测试代码不能正常运行,包括早些版本的Dhrystone。之后Weicker于1988年开发出了2.0版本,并于同年五月开发出了2.1版本,基本解决了这一问题。
Dhrystone所代表的处理器分数比MIPS(million instructions per second 每秒钟执行的指令数)更有意义,因为在不同的指令系统中,比如RISC(Reduced Instruction Set Computer精简指令集计算机)系统和CISC(Complex Instruction Set Computer复杂指令集计算机)系统,Dhrystone的得分更能表现其真正性能。由于在一个高级任务中,RISC可能需要更多的指令,但是其执行的时间可能会比在CISC中的一条指令还要快。由于Dhrystone仅将每秒钟程序执行次数作为指标,所以可以让不同的机器用其自身的方式去完成任务。另一项基于Dhrystone的分数为DMIPS(DhrystoneMIPS),其含义为每秒钟执行Dhrystone的次数除以1757(这一数值来自于VAX 11/780机器,此机器在名义上为1MIPS机器,它每秒运行Dhrystone次数为1757次)。
作为一项基准程序Dhrystone具有以下缺陷:
它的代码与具有代表性的实际程序代码并不相同。
它易受编译器影响。举例来说,在Dhrystone中有大量的字符串复制语句,用来测量字符串复制的性能。然而Dhrystone中字符串的长度不变,并且均开始于自然对齐的边界,这两点便与真实的程序不同。因此一个优化性能好的编译器能够在去掉循环的情形下通过一连串字的移动替代对字符串的复制,这将会块很多,可能会高达30%。所以我们在编译测试程序的时候,如果指定编译器的优化等级的话,我们会发现在不同的优化登记下,表现出来的性能指标会有差别,优化等级越高,性能指标越好,以下是一款芯片在不同的优化登记下的指标参数
Dhrystone代码量过小,在现代CPU中,它能够被放进指令缓存中,所以它并不能严格的测量取指性能。
DMIPS/MHz: 表示 CPU 在每 1MHz 的运行速度下可以执行多少个DMIPS,由于DMIPS与CPU频率具有正相关性,所以这一分数更容易比较不同的CPU在不同的时钟频率下运行Dhrystone的结果。
从上面的几个概念来看,都是为了评估cpu的性能—cpu的计算速度,由此引申开来,在软件行业,都需要一个基准测试程序,来评估某种性能----比如CPU的运算效率或者内存效率,我们称之为benchmark。比如在评估cpu运算速度领域,就有上面提到的dhrystone,coremark,whetstone等等;在评估内存效率上,有mem_test等等;评估web功能的有Apache Benchmark,Redis-Beachmark等等。
1.2 CPU算力DMIPS测试方法
dmips的算法有两种。
一、运行dhrystone,源码在arm官网有下载,分别是dhry.h、dhry_1.c、dhry_2.c三个代码文件。
示例结果(Hikey970芯片算力测算):
CPU主频:0、1、2、3为小核(A53@1.84GHz),4、5、6、7为大核(A73@1.86GHz)
获取主频命令:cat /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq,其中*为cpu编号
算力测算:每个小核算力约4.1KDMIPS;每个大核算力约8.2KDMIPS。
注:如果测算时CPU上运行了其他程序,使用该方法测算的实际是CPU剩余算力。
二、简单测算,使用架构的平均理论ipc,处理器某频率的ipc值进行约算。ipc即instructtion per second,每秒指令数,单位是dmips/mhz。arm的soc比较喜欢用这个参数。
仍以Hikey970为例:大核(A73@1.86GHz),小核(A53@1.84GHz)
按照下图查询A53、A73芯片的ipc分别为2.3KDMIPS/MHz与4.8KDMIPS/MHz,单核A73@1.86GHz算力=4.8*1.86=8.9KDMIPS,单核A53@1.84GHz算力=2.3*1.84=4.23KDMIPS
参考https://trendune.com/arm-cortex-processor-list-specifications-benchmarks-comparison/
CORE | RELEASE | NODE (nm) | ISA | DMIPS/MHz | big.LITTLE | REMARKS |
Cortex A8 | 2005 | – | v7 A | 2 | – | First ARM v7 in 2005 |
Cortex A9 | 2007 | – | v7 A | 2.5 | No | Perf. +50% > A8 |
Cortex A5 | 2009 | – | v7 A | 1.57 | – | Low power, low cost |
Cortex A15 | 2010 | 28 | v7 A | 3.4 | big + A7 | Good performance |
Cortex A7 | 2011 | 28 | v7 A | 1.9 | LITTLE + A15 | Low power, low cost |
Cortex A53 | 2012 | 20 | v8 A | 2.3 | LITTLE + A57 | Low power |
Cortex A57 | 2012 | 20 | v8 A | 4.1 | big + A53 | Good performance |
Cortex A12 | 2013 | – | v7 A | 3.5 | No | – |
Cortex A17 | 2014 | – | v7 A | 3.2 | big + A7 | Low power, low cost |
Cortex A35 | 2015 | – | v8 A | 2.5 | – | – |
Cortex A72 | 2015 | – | v8 A | 4.7 | big | Good performance |
Cortex A32 | 2016 | – | v8 A | 2.3 | – | – |
Cortex A73 | 2016 | – | v8 A | 4.8 | big | Good performance |
Cortex A55 | 2017 | – | v8.2A | 2.7 | big/LITTLE | DMIPS est. |
Cortex A75 | 2017 | 10 | v8.2A | 5.2 | big | DMIPS est. |
Cortex A76 | 2018 | 7/10 | v8.2A | 6.8 | big | DMIPS est. (Perf. +30% of A75) |
Cortex A76AE | 2018 | 7/10 | v8.2A | – | big | – |
Cortex A77 | 2019 | 7 | v8.2A | 8.1 | big | DMIPS est. (Perf. +20% of A76) |
Cortex A78 | 2020 | 5/7 | v8.2A | 9.7 | big | DMIPS est. (Perf. +20% of A77) |
Cortex A78C | 2020 | 5/7 | v8.2 | – | big | – |
Cortex X1 | 2020 | 5/7 | v8.2A | 10.5 | big | DMIPS est. (Perf. +30% of A77) |
Cortex A79 | 2022 | – | – | – | – | – |
Cortex X2 | 2022 | – | – | – | – | – |
高通kryo是基于arm-cortex定制的,参考Kryo CPU numbering in Snapdragon processors explained
注:芯片架构的IPC只是理论值,实际算力受架构实现、CPU缓存等因素影响,测算值只能作为参考。
1.3 引擎算力DMIPS评估方法
以图像处理功能为例,使用某测试程序运行算法,算法占用算力 = 每帧平均耗时 * 处理帧率 * 测试进程CPU占用率 * CPU总算力
公式推导过程
算法占用需求 = 算法cpu占有率 * cpu总算力
算法cpu占有率 = 测试进程cpu占有率 * 算法运行在测试进程中占比
算法运行在测试进程中占比 = 单位时间算法运行时间 / 单位时间 = 每帧平均耗时(单位:s/帧) * 处理帧率(单位:帧/s)/ 1 = 每帧平均耗时 * 处理帧率
=> 算法占用需求 = 每帧平均耗时 * 处理帧率 * 测试进程CPU占用率 * CPU总算力
例
例如,人脸检测功能每帧平均耗时15ms(运行在A53@2.0GHz芯片上),处理帧率25fps,算法运行时CPU占用率80%,
查表计算,得知A53@2.0GHz cpu总算力为2.0*2.3=4.6KDMIPS
于是得出:人脸检测算力需求=0.015*25*80%*4.6=1.38KDMIPS
2、CPU绑核
参考:
https://www.cnblogs.com/mister-w/p/7170652.html
linux下把进程/线程绑定到特定cpu核上运行_阿里的mnn绑定cpu核心后如何影响调度-CSDN博客
2.1 基本概念
什么是绑核?
所谓绑核,其实就是设定某个进程/线程与某个CPU核的亲和力(affinity)。设定以后,Linux调度器就会让这个进程/线程只在所绑定的核上面去运行。但并不是说该进程/线程就独占这个CPU的核,其他的进程/线程还是可以在这个核上面运行的。如果想要实现某个进程/线程独占某个核,就要使用cpuset命令去实现。其实,很多情况下,为了提高性能,Linux调度器会自动的实现尽量让某个进程/线程在同样的CPU上去运行。所以,除非必须,我们没有必要显式的去进程绑核操作。
什么时候需要绑核?
1、当某个进程需要较高的运行效率时,就有必要考虑将其绑定到单独的核上运行,以减小由于在不同的核上调度造成的开销。
2、一些芯片有多个CPU核,并且区分大小核。在做性能分析,验证某个程序在目标平台的算力占用、响应时间时,为避免程序在不同cpu核调度造成测试结果误差,就需要绑定大(小)核进行测试。
2.2 使用taskset命令绑核
1、获取进程pid
-> % ps
PID TTY TIME CMD
2683 pts/1 00:00:00 zsh
2726 pts/1 00:00:00 dgram_servr
2930 pts/1 00:00:00 ps
2、查看进程当前运行在哪个cpu上
-> % taskset -p 2726
pid 2726's current affinity mask: 3
显示的十进制数字3转换为2进制为最低两个是1,每个1对应一个cpu,表示进程运行在2个CPU上(第0和第1个)。
3、指定进程运行在cpu1上
-> % taskset -pc 1 2726
pid 2726's current affinity list: 0,1
pid 2726's new affinity list: 1
注意,cpu的标号是从0开始的,所以cpu1表示第二个cpu(第一个cpu的标号是0)。
至此,就把应用程序绑定到了cpu1上运行,查看如下:
-> % taskset -p 2726
pid 2726's current affinity mask: 2
4、启动程序时绑定cpu
#启动时绑定到第二个cpu
-> % taskset -c 1 ./dgram_servr&
[1] 3011
#查看确认绑定情况
-> % taskset -p 3011
pid 3011's current affinity mask: 2
注:在某些系统上(例如广汽A18Y 820A车机、Hieky970开发板),taskset命令没有-c选项。
如果要绑定cpu,命令如下:
taskset -p mask pid —— 制定pid程序运行在CPU上
taskset mask ./a.out —— 启动程序时绑定CPU
示例:
2.3 使用系统调用绑核
2.3.1 进程绑核-sched_setaffinity
sched_setaffinity可以将某个进程绑定到一个特定的CPU,函数定义:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
/* 设置进程号为pid的进程运行在mask所设定的CPU上
* 第二个参数cpusetsize是mask所指定的数的长度
* 通常设定为sizeof(cpu_set_t)
* 如果pid的值为0,则表示指定的是当前进程
*/
int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);/* 获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中 */
示例代码:
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/sysinfo.h>
#include<unistd.h>
#define __USE_GNU
#include<sched.h>
#include<ctype.h>
#include<string.h>
#include<pthread.h>
#define THREAD_MAX_NUM 200 //1个CPU内的最多进程数
int num=0; //cpu中核数
void* threadFun(void* arg) //arg 传递线程标号(自己定义)
{
cpu_set_t mask; //CPU核的集合
cpu_set_t get; //获取在集合中的CPU
int *a = (int *)arg;
int i;
printf("the thread is:%d\n",*a); //显示是第几个线程
CPU_ZERO(&mask); //置空
CPU_SET(*a,&mask); //设置亲和力值
if (sched_setaffinity(0, sizeof(mask), &mask) == -1)//设置线程CPU亲和力
{
printf("warning: could not set CPU affinity, continuing...\n");
}
CPU_ZERO(&get);
if (sched_getaffinity(0, sizeof(get), &get) == -1)//获取线程CPU亲和力
{
printf("warning: cound not get thread affinity, continuing...\n");
}
for (i = 0; i < num; i++)
{
if (CPU_ISSET(i, &get))//判断线程与哪个CPU有亲和力
{
printf("this thread %d is running processor : %d\n", i,i);
}
}
return NULL;
}
int main(int argc, char* argv[])
{
int tid[THREAD_MAX_NUM];
int i;
pthread_t thread[THREAD_MAX_NUM];
num = sysconf(_SC_NPROCESSORS_CONF); //获取核数
if (num > THREAD_MAX_NUM) {
printf("num of cores[%d] is bigger than THREAD_MAX_NUM[%d]!\n", num, THREAD_MAX_NUM);
return -1;
}
printf("system has %i processor(s). \n", num);
for(i=0;i<num;i++)
{
tid[i] = i; //每个线程必须有个tid[i]
pthread_create(&thread[i],NULL,threadFun,(void*)&tid[i]);
}
for(i=0; i< num; i++)
{
pthread_join(thread[i],NULL);//等待所有的线程结束,线程为死循环所以CTRL+C结束
}
return 0;
}
2.3.2 线程绑核-pthread_setaffinity_np
pthread_setaffinity_np函数定义:
#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.
示例代码:
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
int
main(int argc, char *argv[])
{
int s, j;
cpu_set_t cpuset;
pthread_t thread;
thread = pthread_self();
/* Set affinity mask to include CPUs 0 to 7 */
CPU_ZERO(&cpuset);
for (j = 0; j < 8; j++)
CPU_SET(j, &cpuset);
s = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
if (s != 0)
handle_error_en(s, "pthread_setaffinity_np");
/* Check the actual affinity mask assigned to the thread */
s = pthread_getaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
if (s != 0)
handle_error_en(s, "pthread_getaffinity_np");
printf("Set returned by pthread_getaffinity_np() contained:\n");
for (j = 0; j < CPU_SETSIZE; j++)
if (CPU_ISSET(j, &cpuset))
printf(" CPU %d\n", j);
exit(EXIT_SUCCESS);
}