關於 讓 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 #

edisonx 發表在 痞客邦 PIXNET 留言(0) 人氣()