2009年11月1日 星期日

時間檢測


這是個 nano-second 精度的計時器,所做的僅是計算 MSR 差值。
在 multi-core processors 下,
各 CPU 的 TSC 同步率若不高,計時結果也會有誤差,
故建構 TimeProfile 時,便設法讓行程在單一 CPU 上跑。

設計上很簡便,但存在一些缺點:

1. 當他前幾次執行時,算出來的時間間隔會比較長。
    因為 CPU 要花時間將算法的相關指令與資料 load 到 L2 甚至 L1 裡,
    並調整諸如分支預測等機制的最佳模式。

2. Process 會因 context switch 被切出去,但 MSR 仍持續累加,
    造成計時誤差。為避免此情形,可在檢測算法前,
    將 process 與其中的 main thread 之 priority 提高,
    待算完後再行回復:

    DWORD oldProcess = GetPriorityClass (GetCurrentProcess());
    DWORD oldThread  = GetThreadPriority (GetCurrentThread());

    SetPriorityClass (GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
    ...........[Algorithm]...........
    SetThreadPriority (GetCurrentThread(), oldThread);
    SetPriorityClass (GetCurrentProcess(), oldProcess);

    但這步驟可能會讓其他 thread 無法順暢執行,
    為保持 TimeProfile 的「輕鬆」簡便,便不加上這層考量。

3. 現今許多 CPU 為了省電節能,當處於閒置狀態會降低主頻,
    如: Intel 的 EIST、AMD 的 C&Q,
    故 TimeProfile 中額外定義了 set_freq,
    以便在「需要」時重新讀取 CPU 頻率。

4. 需注意:真正的計時類別尚有許多細節待考慮,
    本程式僅是個以簡便為主的粗糙設計。

#include <stdio.h>
#include <windows.h>

class TimeProfile
{
    unsigned __int64 Hz;
    unsigned __int64 cycle()     //讀取 64-bit 的 
    {                            //Model Specific Register (MSR)
        _asm
        {
            xor   eax, eax
            _emit 0x0F           //使用 cpuid 讓 CPU 不採無序執行指令
            _emit 0xA2           //讓接下來的指令照流水線運行
            _emit 0x0F           //注意: 586 後的 IA32 才支援 RDTSC
            _emit 0x31           //結果置於 EDX:EAX
        }
    }
  public:
    void set_freq()
    {
        LARGE_INTEGER freq;
        QueryPerformanceFrequency (&freq);
        Hz = freq.QuadPart;
    }
    TimeProfile()
    {
        set_freq();
        //若 multi-core 同步率高, 可不用加下面那行
        SetThreadAffinityMask (GetCurrentThread(), 1);
    }
    double time;
    void start() {time = (double) cycle();}
    void end()   {time = double (cycle()-time) /Hz;}
};
測試碼:

double cal_pi (int i = 50)
{
   double pi = 2;
   for (; i>0; i--) 
       pi = pi * double(i)/(2*i+1) + 2;
   return pi;
}

int main (/* daviddr 081211 */)
{
    TimeProfile tp;   
    double pi;

    for (int i=1; i<52; i+=5)
    {
        tp.start();
        pi = cal_pi (i);
        tp.end();
        printf ("\n[%2d] PI = %.15f   Time: %.9f", i, pi, tp.time);
    }   
    return system ("pause");
}
從結果可以觀察到:第一次執行時,
因為 CPU 要做些「準備工作」,算出的時間便較長。

[ 1] PI = 2.666666666666667   Time: 0.000004494
[ 6] PI = 3.132156732156732   Time: 0.000001445
[11] PI = 3.141358472520136   Time: 0.000001507
[16] PI = 3.141586396037061   Time: 0.000001674
[21] PI = 3.141592479958224   Time: 0.000001756
[26] PI = 3.141592648659952   Time: 0.000001881
[31] PI = 3.141592653447636   Time: 0.000002006
[36] PI = 3.141592653585648   Time: 0.000002130
[41] PI = 3.141592653589671   Time: 0.000002255
[46] PI = 3.141592653589790   Time: 0.000002379
[51] PI = 3.141592653589793   Time: 0.000002504

沒有留言:

張貼留言