www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > 公眾號(hào)精選 > 朱老師IT充電站
[導(dǎo)讀]【說在前面的話】在之前的文章《【嵌入式秘術(shù)】相約榨干SysTick的每一滴汁水》里,我們介紹了一個(gè)以“寄居”形式(也就是在不影響用戶已有SysTick應(yīng)用的情況下)測(cè)量CPU性能的開源函數(shù)庫(kù)perf_counter。其倉(cāng)庫(kù)連接如下:https://github.com/Gorgo...



【說在前面的話】


在之前的文章《【嵌入式秘術(shù)】相約榨干SysTick的每一滴汁水》里,我們介紹了一個(gè)以“寄居”形式(也就是在不影響用戶已有SysTick應(yīng)用的情況下)測(cè)量CPU性能的開源函數(shù)庫(kù) perf_counter。其倉(cāng)庫(kù)連接如下:

https://github.com/GorgonMeducer/perf_counter
不知不覺中,perf_counter已經(jīng)經(jīng)歷了大大小小7個(gè)版本:
  • 提高了delay_us() 的精度


  • 增加了對(duì)GCC、IAR的支持


  • 改進(jìn)了 __cycleof__() 宏,使其支持嵌套、并不再?gòu)?qiáng)制綁定 printf()





如果你使用的是Arm Compiler5(armcc)或是Arm Compiler 6(armclang),移植就特別簡(jiǎn)單。你可以按照這篇文章的手把手教程在5分鐘內(nèi)完成部署。

【關(guān)于對(duì)GCC和IAR的支持】對(duì)于GCC和IAR來說,由于它們都不支持 Arm Compiler 5/6 所特有的 Linker語法——$Sub$$ 和 $Super$$,因此無法直接通過 Lib 的方式實(shí)現(xiàn)對(duì)已有SysTick應(yīng)用的 “寄居”——這里就只能忍痛割愛了。這并不影響我們以源代碼的形式將它們加入已有的 GCC 或是 IAR 工程。大體步驟如下:
第一步:perf_counter.c perf_counter.h 拷貝到你的工程目錄下,并將perf_counter.c 加入到編譯列表中;
第二步:perf_counter.h 所在的路徑加入到編譯器的頭文件搜索路徑中;

第三步perf_counter.c 依賴 CMSIS 5.4.0 及其以上版本,確保你的工程中正確的包含了對(duì)CMSIS的支持。(這里就不再贅述)。



第四步:在需要用到 perf_counter 功能的C源文件中加入對(duì)頭文件的包含:


#include "perf_counter.h"


第五步:一般來說,用戶會(huì)在某一個(gè)地方,比如 main() 函數(shù)內(nèi)完成對(duì)CPU工作頻率的配置,我們應(yīng)該在完成這一工作之后確保全局變量 SystemCoreClock 被正確的更新——保存當(dāng)前CPU的工作頻率,比如:


extern uint32_t SystemCoreClock;void main(void){ system_clock_update(); //! 更新CPU工作頻率 SystemCoreClock = 72000000ul //! 假設(shè)更新后的系統(tǒng)頻率是 72MHz ...} 一般來說,你的芯片工程如果本身都是基于較新的CMSIS框架而創(chuàng)建的,你的啟動(dòng)文件中已經(jīng)為你定義好了全局變量 SystemCoreClock——當(dāng)然,凡事都有例外,如果你在編譯的時(shí)候報(bào)告找不到變量 SystemCoreClock 或者說“Undefined symbol __SystemCoreClock” 之類的,你自己定義一下就好了,比如:


uint32_t SystemCoreClock;void main(void){ system_clock_update(); //! 更新CPU工作頻率 SystemCoreClock = 72000000ul //! 假設(shè)更新后的系統(tǒng)頻率是 72MHz ...} 在這以后,我們需要對(duì) perf_counter 庫(kù)進(jìn)行初始化。這里分兩種情況:



1、用戶自己的應(yīng)用里完全沒有使用SysTick。對(duì)于這種情況,我們要在 main.c (或者別的什么源文件里)添加一個(gè)SysTick中斷處理程序:



#include "perf_counter.h"...
__attribute__((used))    //!< 避免下面的處理程序被編譯器優(yōu)化掉void SysTick_Handler(void){ //! 這個(gè)函數(shù)來自于 perf_counter.h user_code_insert_to_systick_handler();}
然后我們?cè)?main() 函數(shù)里初始化 perf_counter 服務(wù):


#include ...
void main(void){ system_clock_update(); //! 更新CPU工作頻率 SystemCoreClock = 72000000ul //! 假設(shè)更新后的系統(tǒng)頻率是 72MHz init_cycle_counter(false); ...} 需要特別注意的是:由于用戶并沒有自己初始化 SysTick,因此我們需要將這一情況告知 perf_counter 庫(kù)——由它來完成對(duì) SysTick 的初始化——這里傳遞 false 給函數(shù) init_cycle_counter() 就是這個(gè)功能。如果由perf_counter 庫(kù)自己來初始化SysTick,它會(huì)為了自己功能更可靠將 SysTick的溢出值(LOAD寄存器)設(shè)置為最大值(0x00FFFFFF)。



2、用戶自己的應(yīng)用里使用了SysTick,擁有自己的初始化過程。對(duì)于這種情況,我們需要確保一件事情:即,SysTick的CTRL寄存器的 BIT2(SysTick_CTRL_CLKSOURCE_Msk)是否被置位了——如果其值是1,說明SysTick使用了跟CPU一樣的工作頻率,那么SysTick的測(cè)量結(jié)果就是CPU的周期數(shù);如果其值是0,說明SysTick使用了來自于別處的時(shí)鐘源,這個(gè)時(shí)鐘源具體頻率是多少就只能看芯片手冊(cè)了(比如STM32就喜歡將系統(tǒng)頻率做 1/8 分頻后提供給SysTick作為時(shí)鐘源),此時(shí)SysTick測(cè)量出來的結(jié)果就不是CPU的周期數(shù)。


在確保了 CTRL 寄存器的 BIT2 被正確置位,并且SysTick中斷被使能(置位 BIT1,SysTick_CTRL_TICKINT_Msk )后,我們可以簡(jiǎn)單的通過 init_cycle_counter() 函數(shù)告訴perf_counter模塊:SysTick 被用戶占用了——這里傳遞 true 就實(shí)現(xiàn)這一功能。


#include ...
void main(void){ system_clock_update(); //! 更新CPU工作頻率 SystemCoreClock = 72000000ul //! 假設(shè)更新后的系統(tǒng)頻率是 72MHz init_cycle_counter(true); ...} 當(dāng)然,不要忘記向已經(jīng)存在的SysTick_Handler()內(nèi)加入perf_counter()的插入函數(shù):



#include "perf_counter.h"...
__attribute__((used))    //!< 避免下面的處理程序被編譯器優(yōu)化掉void SysTick_Handler(void){ ... //! 這個(gè)函數(shù)來自于 perf_counter.h user_code_insert_to_systick_handler(); ...} 至此,我們就完成了 perf_counter 模塊在 GCCIAR中的部署。




【如何測(cè)量代碼片斷占用了多少CPU資源】


很多時(shí)候,我們會(huì)關(guān)心某一段代碼或者函數(shù)究竟用了多少CPU周期,比如,我們寫了一個(gè)算法,你很擔(dān)心“這個(gè)算法究竟使用了多少CPU資源”,為了解決這個(gè)問題,我們需要用到如下的公式:
CPU資源占用(百分比) =     (函數(shù)運(yùn)行所需的時(shí)間)? (算法運(yùn)行間隔的最小值)    ?? 100%

對(duì)于函數(shù)運(yùn)行所需的時(shí)間算法運(yùn)行間隔的最小值來說,雖然它們都是時(shí)間單位,但考慮到CPU的頻率是給定的(不變的),因此,這里的時(shí)間單位在乘以CPU的工作頻率后都可以被換算為CPU的周期數(shù)。舉例來說,假如算法運(yùn)行間隔的最小值是 20ms、CPU的頻率是72MHz,那么對(duì)應(yīng)的周期數(shù)就是 72000000 * (20ms / 1000ms) = 1440000 個(gè)周期??磥砩鲜龉街形ㄒ恍枰覀儗?shí)際測(cè)量的就是【函數(shù)運(yùn)行所需的周期數(shù)】了。
perf_counter 提供了一個(gè)非常簡(jiǎn)單的運(yùn)算符:__cycleof__()。假設(shè)我們要測(cè)量的代碼片斷如下:


...my_algorithm_step_a();my_algorithm_step_b();...my_algorithm_step_c();... 則我們可以輕松的通過__cycleof__()運(yùn)算來測(cè)量結(jié)果:
...__cycleof__("my algorithm") { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();}... 如果你的系統(tǒng)支持 printf(),則可以看到類似如下的輸出結(jié)果:



帶入上述公式:525139 / 14400000 * 100% ≈ 36.5%

就計(jì)算出這個(gè)算法占用了大約 36.5% 的CPU資源,值得說明的是,從原理上看,這一方式對(duì)裸機(jī)和RTOS同樣有效哦。
有的小伙伴很快會(huì)說,我的系統(tǒng)并不允許我調(diào)用printf,那我還可以使用 __cycleof__() 么?當(dāng)然了!就繼續(xù)以上述代碼為例子:



int32_t nCycleUsed = 0;
...__cycleof__("my algorithm", { nCycleUsed = _; }) { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();}... 這里的代碼所實(shí)現(xiàn)的功能是:
  • 測(cè)量了用戶函數(shù) my_algorithm_step_xxx() 所使用的周期數(shù):


  • 測(cè)量的結(jié)果被轉(zhuǎn)存到了一個(gè)叫做 nCycleUsed 的變量中;


  • __cycleof__() 將不會(huì)調(diào)用 printf() 進(jìn)行任何內(nèi)容輸出。



我相信很多小伙伴會(huì)揉了揉眼睛、仔細(xì)看了又看,然后回過頭來滿頭問號(hào):


這是C語言?
這是什么語法?
不要懷疑,這就是C語言,只不過使用了一點(diǎn)GCC的語法擴(kuò)展(感興趣的小伙伴可以復(fù)制這里的連接 https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs),考慮到本文只介紹 perf_counter 如何使用,而對(duì)其如何實(shí)現(xiàn)的并不關(guān)心,我們不妨略過GCC擴(kuò)展語法的部分,專門來看看上述代碼的使用細(xì)節(jié):
  • 首先,為了方便大家觀察,我們先忽略圓括號(hào)內(nèi)的部分:


...__cycleof__(...) { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();}... 可以發(fā)現(xiàn),這里跟此前并沒有什么不同:花括號(hào)包圍的部分就是我們要測(cè)量的代碼片斷;

  • 接下來,我們專門來看__cycleof__() 圓括號(hào)中的部分:


int32_t nCycleUsed = 0;
...__cycleof__("my algorithm", { nCycleUsed = _; }){...}... 容易發(fā)現(xiàn),如果以“,” 為分隔符,那么實(shí)際傳遞給 __cycleof__() 的是兩個(gè)部分:
1、標(biāo)注測(cè)量名稱的字符串
"my algorithm" 2、一段用花括號(hào)括起來的代碼片斷:
{nCycleUsed = _;} 其中,nCycleUsed 是一個(gè)事先已經(jīng)初始化好的變量。
這里,對(duì)于表示測(cè)量名稱的字符串"my algorithm",在這一用法下在最終的編譯結(jié)果里并不會(huì)占用任何RAM或者是ROM,但作為語法結(jié)構(gòu)是必須的。
對(duì)于花括號(hào)所囊括的代碼片段來說,實(shí)際上在這個(gè)花括號(hào)里,你幾乎可以為所欲為:
  • 你可以寫任意數(shù)量的代碼


  • 你可以調(diào)用函數(shù)


  • 你可以定義變量(當(dāng)然這里定義變量肯定就是局部變量了)



但我們一般要做的事情其實(shí)是通過__cycleof__() 所定義的一個(gè)局部變量"_"來獲取測(cè)量結(jié)果——這也是下面代碼的本意:


nCycleUsed = _; 需要說明的是,這個(gè)局部變量"_"生命周期僅限于這個(gè)花括號(hào)中,因此不會(huì)影響 __cycleof__() 整個(gè)結(jié)構(gòu)之外的部分——或者說,下述代碼是沒有意義的:

int32_t nCycleUsed = 0;
...__cycleof__("my algorithm", { nCycleUsed = _; }) { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();}
printf("Cycle Used %d", _);
編譯器會(huì)毫不客氣的告訴你 "_" 是一個(gè)未定義的變量,反之如果你這么做:
int32_t nCycleUsed = 0;
...__cycleof__("my algorithm", { nCycleUsed = _; printf("Cycle Used %d", _); }) { my_algorithm_step_a(); my_algorithm_step_b(); ... my_algorithm_step_c();} 則會(huì)看到你心怡的輸出結(jié)果:




【沒有什么黑魔法】


如果你對(duì)上述例子的等效形式(展開形式)感到非常好奇,其實(shí)大可不必,上述代碼在“邏輯上等效”于如下的形式:
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); 是不是突然就沒有那么神秘了?通過“邏輯等效”的形式展開,我們很容易發(fā)現(xiàn)一些有趣的內(nèi)容:
  • 起核心作用的是一個(gè)叫做 get_system_ticks() 的函數(shù)。實(shí)際上它返回的是從復(fù)位后 SysTick被使能至今所經(jīng)歷的 CPU 周期數(shù)——由于它是int64_t 的類型,因此不用擔(dān)心超過 SysTick 24位計(jì)數(shù)器的量程,也不用擔(dān)心人類歷史范圍內(nèi)會(huì)發(fā)生溢出的可能。 知道這一點(diǎn)后,聰明的小伙伴就可以自己整活兒了。


  • 由于 "_" 是一個(gè)局部變量,因此可以判斷 __cycleof__() 是支持嵌套的



需要特別說明的是,get_system_tick() 函數(shù)自己也是有CPU時(shí)鐘開銷的,所以如果要獲得較為精確的結(jié)果,推薦通過下面的方法來獲取校準(zhǔn)值:


static int64_t s_lPerfCalib;
void calib_perf_counter(void) { int64_t lTemp = get_system_tick(); s_lPerfCalib = get_system_tick() - lTemp;}
int64_t get_perf_counter_calib(void){ return s_lPerfCalib;} 具體如何使用,這里就不再贅述了。

【說在后面的話】perf_counter 仍然在不停的演化中,這多虧了開源社區(qū)不斷的使用和反饋。perf_counter 的應(yīng)用場(chǎng)景實(shí)際上非常廣泛,包括但不限于:
  • 為裸機(jī)或者RTOS提供Cycle級(jí)別的性能測(cè)量;


  • 評(píng)估代碼片段的CPU占用;


  • 算法精細(xì)優(yōu)化時(shí)用于測(cè)量和觀察優(yōu)化的效果;


  • 測(cè)量中斷的響應(yīng)時(shí)間;


  • 測(cè)量中斷的發(fā)生間隔(查找最短時(shí)間間隔);


  • 評(píng)估GUI的幀率或者刷新率;


  • 與SystemCoreClock計(jì)算后,獲得一個(gè)系統(tǒng)時(shí)間戳(Timestamp);


  • 當(dāng)做Realtime Clock的基準(zhǔn);


  • 作為隨機(jī)數(shù)種子


  • ……



實(shí)際上perf_counter在我參與的另外一個(gè)開源項(xiàng)目 arm-2d里也被悄悄的藏在了 platform_utilities.lib 中用來為例子代碼提供幀率的測(cè)量服務(wù)。





end




敬請(qǐng)大家星標(biāo)關(guān)注公眾號(hào)《朱老師IT充電站》。



本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉