關於 讓 CPU 使用率曲線聽你指揮 之相關文章中,已對原作之解法提出分析與實做,接下來繼續討論相關之議題。原作提出之議題吾人大致分成以下四部份:
1. 取得 CPU 核心週期數
2. 取得 CPU 使用率之方法
3. 高精度計時器
* 4. 取得 CPU 時脈率
由於原作只有提到可用 1. 之方法取得 CPU 時脈率,但實做時容易遇到問題,於是吾人於第四部份予以探討與實做。
1 . 取得 CPU 時脈核心週期數
原作程式碼,加以修改後如下所示
__int64 GetCPUTickCount() { __asm { rdtsc; } } // x64 #if defined(_M_X64) #define GetCPUTickCount() __rdtsc() #endif
重點是這要怎麼用?原作只有提到可用 CallNTPowerInformation 得到頻率,將週期數化為毫秒數,程式碼如下所述
_PROCESSOR_POWER_INFORMATION info; CallNTPowerInformation(11, NULL, 0, &info, sizeof(info) ); __int64 t_begin = GetCPUTickCount(); // do something __int64 t_end = GetCPUTickCount(); double millisec = \ ((double)t_end-(double)t_begin)/info.CurrentMhz;
但實做上將遇到不少困難,這裡放到第四部份再詳細說明。
2. 取得 CPU 使用率之方法
這裡已於另一文說明,請參照 這篇 文章,裡面 [解法三實測與評論] 已提供 cpu.h 取得。
3. 高精度計時器
對於計時器之精度等測試問題,可詳見 計時器整理 之文章,裡面提出了五種方式之實做與說明。這裡再做的是另一項補充。
在做計時動作時,常是這麼做 (以 clock() 為例)
#include <stdio.h> #include <windows.h> #include <time.h> int main() { clock_t t1, t2; t1 = clock(); Sleep(1234); t2 = clock(); printf("%lf\n", (double)(t2-t1)/CLK_TCK); return 0; }
這裡有個地方要修正
clock_t t0, t1, t2; t0 = clock(); while( (t1 = clock())<=t0); Sleep(1234); t2 = clock();
會這麼做是為了希望起始之 clock 為在時間之頭,而不是在尾,盡可能減少計時上之誤差。
* 4. 取得 CPU 時脈率
方法一:使用 CallNTPowerInformation
這方法為作者所提出,由於實作上不少人都遇到問題,吾人亦摸索了一段時間,這裡將實作過程放上來。
首先在 MSDN 上註明了,使用這 API 必須有 PowrProf.h、PowerProf.lib、PowrProf.dll,程式碼中直接只寫這段看看
#pragma comment(lib, "pwrprof.lib") #include <windows.h> #include <powrprof.h> int main() { _PROCESSOR_POWER_INFORMATION info; return 0; }
很遺憾的是,compiler 竟出現了錯誤訊息:無法開啟檔案 'pwrprof.lib';
#pragma 拿掉,則又會出現了另一錯誤訊息:'PROCESSOR_POWER_INFORMATION' : 未宣告的識別項
這問題應是 compiler 環境操作不熟導致,首先,這部份不適合用 #pragma 去連結 lib,直接用 IDE 方式去連結,連結方式:
[專案]->[屬性]->[組態屬性]->[連結器]->[命令列]-> 右邊下面輸入 Powrprof.lib
好了之後再 compiler 一次,又發現還是沒辦法解決 'PROCESSOR_POWER_INFORMATION' : 未宣告的識別項 之錯誤訊息,原因在於吾人環境並沒對 PROCESSOR_POWER_INFORMATION 進行宣告(定義),於是只好自己加進去。
考慮了以上之情形,實做後完成之程式碼如下所示
#include <windows.h> #include <powrprof.h> #include <stdio.h> typedef struct _PROCESSOR_POWER_INFORMATION { ULONG Number; ULONG MaxMhz; ULONG CurrentMhz; ULONG MhzLimit; ULONG MaxIdleState; ULONG CurrentIdleState; } PROCESSOR_POWER_INFORMATION , *PPROCESSOR_POWER_INFORMATION; // main function int main() { PROCESSOR_POWER_INFORMATION info; CallNtPowerInformation(ProcessorInformation, NULL, 0, &info, sizeof(info)); printf("MaxMhz:%lu\n", info.MaxMhz); printf("currentMhz:%lu\n", info.CurrentMhz); printf("MhzLimit:%lu\n", info.MhzLimit); return 0; }
執行結果 (吾人 cpu 為 2.91 GHz 雙核 )
MaxMhz: 2913
currentMhz:2913
MhzLimit:2913
方法二: rdtsc 算時脈率
考慮下面程式碼
#include <windows.h> #include <stdio.h> // main function int main() { unsigned __int64 t0, t1, t2, td; double clk_t=0.0; // sec per clock t0 = __rdtsc(); while( (t1=__rdtsc()) <= t0); Sleep(1000); t2 = __rdtsc(); td = t2-t1; clk_t = 1.0 / td; printf("clock cycle freq: %lld hz\n", td); printf("clock cycle freq: %lf Ghz\n", 1E-9*td); printf("clock cycle time: %E sec\n", clk_t); return 0; }
執行結果
clock cycle freq: 2912869852 hz
clock cycle freq: 2.912870 Ghz
clock cycle time: 3.433040E-010 sec
這結果與調用 CallNtPowerInformation 相似,但 CallNtPowerInformation 所得到之時脈率是以 Mhz 為單位,而使用此方法是以 hz 為單位。用此法知道時脈後,要進行計時的話就方便多了
#include <windows.h> #include <stdio.h> // main function int main() { // freopen("out.txt", "w", stdout); unsigned __int64 t0, t1, t2, td; double clk_t=0.0; // sec per clock /* 計算時脈 */ t0 = __rdtsc(); while( (t1=__rdtsc()) <= t0); Sleep(1000); t2 = __rdtsc(); td = t2-t1; clk_t = 1.0 / td; /* 計時 1sec */ t0 = __rdtsc(); while( (t1=__rdtsc()) <= t0); Sleep(1234); t2 = __rdtsc(); printf("%lf \n", (double)(t2-t1)/td); return 0; }
執行結果
1.235506
上面的計時結果並不是非常準,因在計算時脈率時,使用的是 Sleep 函式,但 Sleep 誤差為數十個 ms ,就使用 __rdtsc 系列要精準到奈秒(10^-9) 級而言,似乎不恰當。有個簡單的方法可以改善,即為在測時偵測時脈率時,將 Sleep 時間拉長,即從原本的 1000 整個拉長,至於要拉長多久,乃視想達到多少精度為主。假定 Sleep 誤差為 40ms,上面是 Sleep(1000),誤差即為 4%;若為 Sleep(40000),誤差則降為 0.1 %,但相對的,要得到精準之時脈率,就要有愈長時間測時。
※ 少用 __rdtsc()
這點有爭議。
MSDN 上有對此方式提出三大點問題,這裡摘要說明約如下述敘
1. 所取之值不連續:直接使用 RDTSC 乃假設執行緒是在同一個 processor 下執行不進行轉移,但多核系統並不保證 processor 間之計時器為同步。
2. 相依於硬體:雖 RDTSC 長期為高精度計時之方法,但較新的主機版已不用 RDTSC 計時,而是用其他之高精度方式計時。
3. CPU 頻率之變異:使用此方式計時之前題為,在此程式之生命週期期間, CPU 頻率為固定不變。但實際上並非如此,CPU 頻率可能會因系統供電及負荷情況有所調整。
鑑於上述 3 點,於是 MSDN 上建議使用的高精度計時器為 QueryPerformanceFrequency、QueryPerformanceCounter 方式。
以 QueryPerformanceFrequency、QueryPerformanceCounter 或用 RDTSC 方式測時之好壞,上述已有初步之討論,至於再深入之應用可再進修;吾人暫不於此進行此二方法優劣之評論 。
# E.N.D #