0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

perf_counter在测量系统的性能中有何作用?

嵌入式程序员 来源:裸机思维 作者:裸机思维 2021-06-11 12:54 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

不知不觉中,perf_counter已经经历了大大小小7个版本:

提高了delay_us()的精度

增加了对GCC、IAR的支持

改进了__cycleof__()宏,使其支持嵌套、并不再强制绑定 printf()

如果你使用的是Arm Compiler5(armcc)或是Arm Compiler 6(armclang),移植就特别简单。你可以按照这篇文章的手把手教程在5分钟内完成部署。

对于GCC和IAR来说,由于它们都不支持 Arm Compiler5/6 所特有的Linker语法——$Sub$$和$Super$$,因此无法直接通过 Lib 的方式实现对已有SysTick应用的 “寄居”——这里就只能忍痛割爱了。 这并不影响我们以源代码的形式将它们加入已有的 GCC 或是 IAR 工程。大体步骤如下: 第一步:将perf_counter.c和perf_counter.h拷贝到你的工程目录下,并将perf_counter.c 加入到编译列表中; 第二步:将perf_counter.h所在的路径加入到编译器的头文件搜索路径中;

第三步:perf_counter.c依赖CMSIS5.4.0及其以上版本,确保你的工程中正确的包含了对CMSIS的支持。(这里就不再赘述)。

第四步:在需要用到perf_counter功能的C源文件中加入对头文件的包含:

#include "perf_counter.h"

第五步:一般来说,用户会在某一个地方,比如main()函数内完成对CPU工作频率的配置,我们应该在完成这一工作之后确保全局变量SystemCoreClock被正确的更新——保存当前CPU的工作频率,比如:

externuint32_tSystemCoreClock;voidmain(void){system_clock_update();//!更新CPU工作频率SystemCoreClock=72000000ul//!假设更新后的系统频率是 72MHz...}

一般来说,你的芯片工程如果本身都是基于较新的CMSIS框架而创建的,你的启动文件中已经为你定义好了全局变量SystemCoreClock——当然,凡事都有例外,如果你在编译的时候报告找不到变量SystemCoreClock或者说“Undefined symbol __SystemCoreClock” 之类的,你自己定义一下就好了,比如:

uint32_tSystemCoreClock;voidmain(void){system_clock_update();//!更新CPU工作频率SystemCoreClock=72000000ul//!假设更新后的系统频率是 72MHz...}

在这以后,我们需要对perf_counter库进行初始化。这里分两种情况:

1、用户自己的应用里完全没有使用SysTick。对于这种情况,我们要在 main.c (或者别的什么源文件里)添加一个SysTick中断处理程序:

#include "perf_counter.h"... __attribute__((used))//!< 避免下面的处理程序被编译器优化掉void SysTick_Handler(void){ //! 这个函数来自于 perf_counter.h user_code_insert_to_systick_handler();}

然后我们在main()函数里初始化perf_counter服务:

#include... voidmain(void){system_clock_update(); //!更新CPU工作频率SystemCoreClock=72000000ul//!假设更新后的系统频率是 72MHzinit_cycle_counter(false);...}

需要特别注意的是:由于用户并没有自己初始化SysTick,因此我们需要将这一情况告知perf_counter库——由它来完成对SysTick的初始化——这里传递false给函数init_cycle_counter()就是这个功能。如果由perf_counter 库自己来初始化SysTick,它会为了自己功能更可靠将 SysTick的溢出值(LOAD寄存器)设置为最大值(0x00FFFFFF)。

2、用户自己的应用里使用了SysTick,拥有自己的初始化过程。对于这种情况,我们需要确保一件事情:即,SysTick的CTRL寄存器的 BIT2(SysTick_CTRL_CLKSOURCE_Msk)是否被置位了——如果其值是1,说明SysTick使用了跟CPU一样的工作频率,那么SysTick的测量结果就是CPU的周期数;如果其值是0,说明SysTick使用了来自于别处的时钟源,这个时钟源具体频率是多少就只能看芯片手册了(比如STM32就喜欢将系统频率做 1/8 分频后提供给SysTick作为时钟源),此时SysTick测量出来的结果就不是CPU的周期数。

在确保了CTRL寄存器的BIT2被正确置位,并且SysTick中断被使能(置位BIT1,SysTick_CTRL_TICKINT_Msk)后,我们可以简单的通过init_cycle_counter()函数告诉perf_counter模块:SysTick被用户占用了——这里传递 true 就实现这一功能。

#include ... void main(void){ system_clock_update(); //! 更新CPU工作频率 SystemCoreClock = 72000000ul //! 假设更新后的系统频率是 72MHz init_cycle_counter(true); ...}

当然,不要忘记向已经存在的SysTick_Handler()内加入perf_counter()的插入函数:

#include "perf_counter.h"... __attribute__((used))//!< 避免下面的处理程序被编译器优化掉void SysTick_Handler(void){ ... //! 这个函数来自于 perf_counter.h user_code_insert_to_systick_handler();    ...}

至此,我们就完成了perf_counter模块在GCC和IAR中的部署。

如何测量代码片断占用了多少CPU资源?

很多时候,我们会关心某一段代码或者函数究竟用了多少CPU周期,比如,我们写了一个算法,你很担心“这个算法究竟使用了多少CPU资源”,为了解决这个问题,我们需要用到如下的公式: CPU资源占用(百分比) = (函数运行所需的时间)(算法运行间隔的最小值) 100% 对于【函数运行所需的时间】和【算法运行间隔的最小值】来说,虽然它们都是时间单位,但考虑到CPU的频率是给定的(不变的),因此,这里的时间单位在乘以CPU的工作频率后都可以被换算为CPU的周期数。举例来说,假如【算法运行间隔的最小值】是 20ms、CPU的频率是72MHz,那么对应的周期数就是 72000000* (20ms / 1000ms) = 1440000 个周期。看来上述公式中唯一需要我们实际测量的就是【函数运行所需的周期数】了。

perf_counter提供了一个非常简单的运算符:__cycleof__()。假设我们要测量的代码片断如下:

...my_algorithm_step_a();my_algorithm_step_b();...my_algorithm_step_c();...则我们可以轻松的通过__cycleof__()运算来测量结果:

...__cycleof__("myalgorithm"){ my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();}...如果你的系统支持printf(),则可以看到类似如下的输出结果:

poYBAGDC7bWAAtn6AABBJkEIg2I291.jpg

带入上述公式:525139 /14400000 * 100% ≈36.5% 就计算出这个算法占用了大约36.5%的CPU资源,值得说明的是,从原理上看,这一方式对裸机和RTOS同样有效哦。

有的小伙伴很快会说,我的系统并不允许我调用printf,那我还可以使用__cycleof__()么?当然了!就继续以上述代码为例子:

int32_tnCycleUsed=0; ...__cycleof__("my algorithm", { nCycleUsed = _; }) { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();}...这里的代码所实现的功能是:

测量了用户函数my_algorithm_step_xxx()所使用的周期数:

测量的结果被转存到了一个叫做nCycleUsed的变量中;

__cycleof__()将不会调用printf()进行任何内容输出。

我相信很多小伙伴会揉了揉眼睛、仔细看了又看,然后回过头来满头问号:

这是C语言? 这是什么语法? 不要怀疑,这就是C语言,只不过使用了一点GCC的语法扩展(感兴趣的小伙伴可以复制这里的连接https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs),考虑到本文只介绍perf_counter如何使用,而对其如何实现的并不关心,我们不妨略过GCC扩展语法的部分,专门来看看上述代码的使用细节:

首先,为了方便大家观察,我们先忽略圆括号内的部分:

...__cycleof__(...){ my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();}...可以发现,这里跟此前并没有什么不同:花括号包围的部分就是我们要测量的代码片断;

接下来,我们专门来看__cycleof__()圆括号中的部分:

int32_tnCycleUsed=0; ...__cycleof__("my algorithm", { nCycleUsed = _;}){...}...容易发现,如果以“,” 为分隔符,那么实际传递给__cycleof__()的是两个部分: 1、标注测量名称的字符串

"my algorithm"2、一段用花括号括起来的代码片断:

{nCycleUsed = _;}其中,nCycleUsed是一个事先已经初始化好的变量。 这里,对于表示测量名称的字符串"my algorithm",在这一用法下在最终的编译结果里并不会占用任何RAM或者是ROM,但作为语法结构是必须的。 对于花括号所囊括的代码片段来说,实际上在这个花括号里,你几乎可以为所欲为:

你可以写任意数量的代码

你可以调用函数

你可以定义变量(当然这里定义变量肯定就是局部变量了)

但我们一般要做的事情其实是通过__cycleof__()所定义的一个局部变量"_"来获取测量结果——这也是下面代码的本意:

nCycleUsed = _;需要说明的是,这个局部变量"_"生命周期仅限于这个花括号中,因此不会影响 __cycleof__() 整个结构之外的部分——或者说,下述代码是没有意义的:

int32_t nCycleUsed = 0; ...__cycleof__("my algorithm", { nCycleUsed = _; }) { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();} printf("Cycle Used %d", _);编译器会毫不客气的告诉你 "_" 是一个未定义的变量,反之如果你这么做:

int32_t nCycleUsed = 0; ...__cycleof__("my algorithm", { nCycleUsed = _; printf("Cycle Used %d", _); }) { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();}

则会看到你心怡的输出结果:

pYYBAGDC7b2AR4b3AAAY48Xf9OE887.jpg

没有什么黑魔法

如果你对上述例子的等效形式(展开形式)感到非常好奇,其实大可不必,上述代码在“逻辑上等效”于如下的形式:

int32_t nCycleUsed = 0; ...do {int64_t_=get_system_ticks(); { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c(); } _ = get_system_ticks() - _; //!我们添加的代码 nCycleUsed = _; printf("Cycle Used %d", _);}while(0);是不是突然就没有那么神秘了?通过“逻辑等效”的形式展开,我们很容易发现一些有趣的内容:

起核心作用的是一个叫做get_system_ticks()的函数。实际上它返回的是从复位后 SysTick被使能至今所经历的 CPU 周期数——由于它是int64_t 的类型,因此不用担心超过 SysTick 24位计数器的量程,也不用担心人类历史范围内会发生溢出的可能。知道这一点后,聪明的小伙伴就可以自己整活儿了。

由于 "_"是一个局部变量,因此可以判断__cycleof__() 是支持嵌套的。

需要特别说明的是,get_system_tick()函数自己也是有CPU时钟开销的,所以如果要获得较为精确的结果,推荐通过下面的方法来获取校准值:

staticint64_ts_lPerfCalib; voidcalib_perf_counter(void){ int64_t lTemp = get_system_tick(); s_lPerfCalib = get_system_tick() - lTemp;} int64_tget_perf_counter_calib(void){return s_lPerfCalib;}具体如何使用,这里就不再赘述了。 说在后面的话。

perf_counter仍然在不停的演化中,这多亏了开源社区不断的使用和反馈。perf_counter的应用场景实际上非常广泛,包括但不限于:

为裸机或者RTOS提供Cycle级别的性能测量;

评估代码片段的CPU占用;

算法精细优化时用于测量和观察优化的效果;

测量中断的响应时间;

测量中断的发生间隔(查找最短时间间隔);

评估GUI的帧率或者刷新率;

与SystemCoreClock计算后,获得一个系统时间戳(Timestamp);

当做Realtime Clock的基准;

作为随机数种子

……

实际上perf_counter在我参与的另外一个开源项目arm-2d里也被悄悄的藏在了platform_utilities.lib中用来为例子代码提供帧率的测量服务。

责任编辑:lq6

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 测量系统
    +关注

    关注

    2

    文章

    569

    浏览量

    43100
  • Counter
    +关注

    关注

    0

    文章

    24

    浏览量

    18441

原文标题:如何“优雅”的测量系统性能

文章出处:【微信号:InterruptISR,微信公众号:嵌入式程序员】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    空气温度测量装置的主要作用是什么?有品牌推荐?

    随着气象学的发展,气象站遍布全球,对空气温度的观测更加系统和准确,市面上可供选择的空气温度测量仪器种类也越来越丰富。那么,空气温度测量装置的主要作用是什么?有
    发表于 10-20 16:24

    PCIe协议分析仪在数据中心中有作用

    升数据中心的整体可靠性。以下是其核心作用及具体应用场景的详细分析:一、性能优化:突破带宽瓶颈,提升计算效率 链路带宽利用率分析 场景:AI训练集群中,GPU通过PCIe与CPU交换数据,若带宽利用率低
    发表于 07-29 15:02

    毫米波雷达自动驾驶中有关键作用

    其中毫米波雷达(Millimeter-Wave Radar)以卓越的抗干扰能力、全天候测距性能和丰富的目标运动信息,成为自动驾驶感知系统中的核心组件之一。相较于激光雷达与摄像头,毫米波雷达的关注度似乎并非那么高,但其作用却无可替
    的头像 发表于 06-28 13:59 1126次阅读
    毫米波雷达<b class='flag-5'>在</b>自动驾驶<b class='flag-5'>中有</b><b class='flag-5'>何</b>关键<b class='flag-5'>作用</b>?

    安泰:水声功率放大器声呐系统中有哪些作用

    水声功率放大器声呐系统中发挥着至关重要的作用,以下是其具体作用的详细介绍: 信号发射方面 增强发射信号强度:声呐系统通过发射声波信号来探测
    的头像 发表于 06-11 15:17 499次阅读
    安泰:水声功率放大器<b class='flag-5'>在</b>声呐<b class='flag-5'>系统</b><b class='flag-5'>中有</b>哪些<b class='flag-5'>作用</b>

    拆解小米 CyberGear 微电机!ams AS5047P 磁编凭性能狂飙?

    《拆解小米 CyberGear 微电机!ams AS5047P 磁编凭性能狂飙?》
    的头像 发表于 05-14 10:45 936次阅读
    拆解小米 CyberGear 微电机!ams AS5047P 磁编凭<b class='flag-5'>何</b>让<b class='flag-5'>性能</b>狂飙?

    时钟同步通信系统中有哪些重要作用

    时钟同步是指在一个系统中,各个时钟能够准确地显示相同的时间。现代科技发展中,时钟同步是非常重要的,特别是计算机网络和通信系统中。计算机
    的头像 发表于 04-29 13:44 929次阅读
    时钟同步<b class='flag-5'>在</b>通信<b class='flag-5'>系统</b><b class='flag-5'>中有</b>哪些重要<b class='flag-5'>作用</b>?

    四探针电极多功能压力测量系统中的原理与应用

    一、引言 多功能压力测量系统里,四探针电极以其独特测量原理,助力获取材料电学性能与压力的关联数据。它在材料科学、电子工程等领域应用广泛,有
    的头像 发表于 03-27 14:04 801次阅读
    四探针电极<b class='flag-5'>在</b>多功能压力<b class='flag-5'>测量</b><b class='flag-5'>系统</b>中的原理与应用

    电压跟随器测量系统中的应用

    电压跟随器测量系统中有着广泛的应用,主要得益于其独特的性能特点,如高输入阻抗、低输出阻抗、电压增益接近1等。以下是对电压跟随器
    的头像 发表于 02-18 16:08 944次阅读

    软件芯片设计中有什么作用

    ,软件是汽车的驾驶系统”。硬件提供了基础设施和功能,而软件则控制和指挥硬件去完成具体的任务。 1. 软件芯片设计中的作用: (1)验证芯片设计: 芯片设计完成后,工程师需要验证芯片是否按照预期工作。虽然硬件设计完成后
    的头像 发表于 02-09 09:43 1420次阅读

    MOS管不同电路中有什么作用

    MOS管,全称金属-氧化物-半导体场效应晶体管(Metal-Oxide-Semiconductor Field-Effect Transistor),是电子电路中非常重要的一种元件。它在不同电路中具有多种作用
    的头像 发表于 01-17 14:19 2430次阅读

    MOS电机驱动中有什么作用

    随着现代工业和自动化技术的迅猛发展,电机驱动系统许多应用中发挥着至关重要的作用。电机驱动不仅广泛应用于工业机器人、自动化生产线、家电等领域,还成为电动汽车、风能发电等新能源技术的重要组成部分
    的头像 发表于 01-08 10:39 2648次阅读
    MOS<b class='flag-5'>在</b>电机驱动<b class='flag-5'>中有</b>什么<b class='flag-5'>作用</b>

    ADS1291中有个Offset Calibration的命令起什么作用

    我在用ADS1291和AFE4404组合做一个产品,使用中遇到些问题: 第1:ADS1291中有个Offset Calibration的命令起什么作用?何时使用,是启动时校准一次还是要周期性地
    发表于 01-01 06:47

    ATA-4014C高压功率放大器综合磁性能测试中有应用

    什么是综合磁性能测试?该测试具体包含什么项目?它主要有哪些应用?功率放大器作为该领域研究中常用的一种电子仪器仪表,综合磁性能测试中有应用
    的头像 发表于 12-24 13:50 572次阅读
    ATA-4014C高压功率放大器<b class='flag-5'>在</b>综合磁<b class='flag-5'>性能</b>测试<b class='flag-5'>中有</b><b class='flag-5'>何</b>应用

    【敏矽微ME32G070开发板免费体验】使用coremark测试敏矽微ME32G070 跑分

    。 1.首先我们KEIL的 中勾选core 2.在下面选项中勾选中perf_counter的core和coremark 3.点击OK,来到KEIL工程 头文件里包含 4.主函数中添加
    发表于 12-19 12:09

    自动驾驶测试设计环节中有关键作用

    自动驾驶测试不仅仅是验证一个产品或技术是否达标,它贯穿了整个产品生命周期。从早期的算法设计,到中期的系统集成验证,再到最终的实车评估,测试始终每个阶段扮演不可或缺的角色。随着自动驾驶技术复杂性增加
    的头像 发表于 12-09 15:34 1308次阅读