日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

讓CPU占用率曲線聽你指揮

 昵稱57922 2008-03-21

寫一個程序,讓用戶來決定Windows任務(wù)管理器(Task Manager)的CPU占用率。程序越精簡越好,計算機(jī)語言不限。例如,可以實現(xiàn)下面三種情況:

1.    CPU的占用率固定在50%,為一條直線;

2.    CPU的占用率為一條直線,但是具體占用率由命令行參數(shù)決定(參數(shù)范圍1~ 100);

3.    CPU的占用率狀態(tài)是一個正弦曲線。

分析與解法

有一名學(xué)生寫了如下的代碼:

while (true)
{
   if (busy)
       i++;
   else
}

然后她就陷入了苦苦思索:else干什么呢?怎么才能讓電腦不做事情呢?CPU使用率為0的時候,到底是什么東西在用CPU?另一名學(xué)生花了很多時間構(gòu)想如何“深入內(nèi)核,以控制CPU占用率”——可是事情真的有這么復(fù)雜么?

MSRA TTG(Microsoft Research Asia, Technology Transfer Group)的一些實習(xí)生寫了各種解法,他們寫的簡單程序可以達(dá)到如圖1-1所示的效果。

圖1-1  編碼控制CPU占用率呈現(xiàn)正弦曲線形態(tài)

看來這并不是不可能完成的任務(wù)。讓我們仔細(xì)地回想一下寫程序時曾經(jīng)碰到的問題,如果我們不小心寫了一個死循環(huán),CPU占用率就會跳到最高,并且一直保持100%。我們也可以打開任務(wù)管理器,實際觀測一下它是怎樣變動的。憑肉眼觀察,它大約是1秒鐘更新一次。一般情況下,CPU使用率會很低。但是,當(dāng)用戶運(yùn)行一個程序,執(zhí)行一些復(fù)雜操作的時候,CPU的使用率會急劇升高。當(dāng)用戶晃動鼠標(biāo)時,CPU的使用率也有小幅度的變化。

那當(dāng)任務(wù)管理器報告CPU使用率為0的時候,誰在使用CPU呢?通過任務(wù)管理器的“進(jìn)程(Process)”一欄可以看到,System Idle Process占用了CPU空閑的時間——這時候大家該回憶起在“操作系統(tǒng)原理”這門課上學(xué)到的一些知識了吧。系統(tǒng)中有那么多進(jìn)程,它們什么時候能“閑下來”呢?答案很簡單,這些程序或者在等待用戶的輸入,或者在等待某些事件的發(fā)生(WaitForSingleObject()),或者進(jìn)入休眠狀態(tài)(通過Sleep()來實現(xiàn))。

在任務(wù)管理器的一個刷新周期內(nèi),CPU忙(執(zhí)行應(yīng)用程序)的時間和刷新周期總時間的比率,就是CPU的占用率,也就是說,任務(wù)管理器中顯示的是每個刷新周期內(nèi)CPU占用率的統(tǒng)計平均值。因此,我們寫一個程序,讓它在任務(wù)管理器的刷新期間內(nèi)一會兒忙,一會兒閑,然后通過調(diào)節(jié)忙/閑的比例,就可以控制任務(wù)管理器中顯示的CPU占用率。

【解法一】簡單的解法

步驟1    要操縱CPU的usage曲線,就需要使CPU在一段時間內(nèi)(根據(jù)Task        Manager的采樣率)跑busy和idle兩個不同的loop,從而通過不同的時間      比例,來獲得調(diào)節(jié)CPU Usage的效果。

步驟2    Busy loop可以通過執(zhí)行空循環(huán)來實現(xiàn),idle可以通過Sleep()來實現(xiàn)。

問題的關(guān)鍵在于如何控制兩個loop的時間,方法有二:

Sleep一段時間,然后以for循環(huán)n次,估算n的值。

那么對于一個空循環(huán)for(i = 0; i < n; i++);又該如何來估算這個最合適的n值呢?我們都知道CPU執(zhí)行的是機(jī)器指令,而最接近于機(jī)器指令的語言是匯編語言,所以我們可以先把這個空循環(huán)簡單地寫成如下匯編代碼后再進(jìn)行分析:

loop:

mov dx i         ;將i置入dx寄存器

inc dx           ;將dx寄存器加1

mov i dx         ;將dx中的值賦回i

cmp i n          ;比較i和n

jl loop          ;i小于n時則重復(fù)循環(huán)

假設(shè)這段代碼要運(yùn)行的CPU是P4 2.4Ghz(2.4 * 10的9次方個時鐘周期每秒)?,F(xiàn)代CPU每個時鐘周期可以執(zhí)行兩條以上的代碼,那么我們就取平均值兩條,于是讓(2 400 000 000 * 2)/5=960 000 000(循環(huán)/秒),也就是說CPU 1秒鐘可以運(yùn)行這個空循環(huán)960 000 000次。不過我們還是不能簡單地將n = 60 000 000,然后Sleep(1000)了事。如果我們讓CPU工作1秒鐘,然后休息1秒鐘,波形很有可能就是鋸齒狀的——先達(dá)到一個峰值(大于>50%),然后跌到一個很低的占用率。

我們嘗試著降低兩個數(shù)量級,令n = 9 600 000,而睡眠時間相應(yīng)改為10毫秒(Sleep(10))。用10毫秒是因為它不大也不小,比較接近Windows的調(diào)度時間片。如果選得太?。ū热?毫秒),則會造成線程頻繁地被喚醒和掛起,無形中又增加了內(nèi)核時間的不確定性影響。最后我們可以得到如下代碼:

代碼清單1-1

int main()

{

    for(;;)

    {

        for(int i = 0; i < 9600000; i++);

        Sleep(10);

    }

    return 0;

}

在不斷調(diào)整9 600 000的參數(shù)后,我們就可以在一臺指定的機(jī)器上獲得一條大致穩(wěn)定的50% CPU占用率直線。

使用這種方法要注意兩點(diǎn)影響:

1.    盡量減少sleep/awake的頻率,如果頻繁發(fā)生,影響則會很大,因為此時優(yōu)先級更高的操作系統(tǒng)內(nèi)核調(diào)度程序會占用很多CPU運(yùn)算時間。

2.    盡量不要調(diào)用system call(比如I/O這些privilege instruction),因為它也會導(dǎo)致很多不可控的內(nèi)核運(yùn)行時間。

該方法的缺點(diǎn)也很明顯:不能適應(yīng)機(jī)器差異性。一旦換了一個CPU,我們又得重新估算n值。有沒有辦法動態(tài)地了解CPU的運(yùn)算能力,然后自動調(diào)節(jié)忙/閑的時間比呢?請看下一個解法。

【解法二】使用GetTickCount()和Sleep()

我們知道GetTickCount()可以得到“系統(tǒng)啟動到現(xiàn)在”的毫秒值,最多能夠統(tǒng)計到49.7天。另外,利用Sleep()函數(shù),最多也只能精確到1毫秒。因此,可以在“毫秒”這個量級做操作和比較。具體如下:

利用GetTickCount()來實現(xiàn)busy loop的循環(huán),用Sleep()實現(xiàn)idle loop。偽代碼如下:

代碼清單1-2

int busyTime = 10;  //10 ms

int idleTime = busyTime;  //same ratio will lead to 50% cpu usage

Int64 startTime = 0;       

while (true)

{

startTime = GetTickCount();

// busy loop的循環(huán)

while ((GetTickCount() - startTime) <= busyTime) ;

    //idle loop

    Sleep(idleTime);

}

這兩種解法都是假設(shè)目前系統(tǒng)上只有當(dāng)前程序在運(yùn)行,但實際上,操作系統(tǒng)中有很多程序都會在不同時間執(zhí)行各種各樣的任務(wù),如果此刻其他進(jìn)程使用了10% 的CPU,那我們的程序應(yīng)該只能使用40%的CPU(而不是機(jī)械地占用50%),這樣可達(dá)到50%的效果。

怎么做呢?

我們得知道“當(dāng)前CPU占用率是多少”,這就要用到另一個工具來幫忙——Perfmon.exe。

Perfmon是從Windows NT開始就包含在Windows服務(wù)器和臺式機(jī)操作系統(tǒng)的管理工具組中的專業(yè)監(jiān)視工具之一(如圖1-2所示)。Perfmon可監(jiān)視各類系統(tǒng)計數(shù)器,獲取有關(guān)操作系統(tǒng)、應(yīng)用程序和硬件的統(tǒng)計數(shù)字。Perfmon的用法相當(dāng)直接,只要選擇您所要監(jiān)視的對象(比如:處理器、RAM或硬盤),然后選擇所要監(jiān)視的計數(shù)器(比如監(jiān)視物理磁盤對象時的平均隊列長度)即可。還可以選擇所要監(jiān)視的實例,比如面對一臺多CPU服務(wù)器時,可以選擇監(jiān)視特定的處理器。

Figure 1: System Monitor (Perfmon)

圖1-2  系統(tǒng)監(jiān)視器(Perfmon)

我們可以寫程序來查詢Perfmon的值,Microsoft .Net Framework提供了PerformanceCounter()這一類型,從而可以方便地拿到當(dāng)前各種計算機(jī)性能數(shù)據(jù),包括CPU的使用率。例如下面這個程序——

【解法三】能動態(tài)適應(yīng)的解法

代碼清單1-3

//C# code  

static void MakeUsage(float level)

{

   PerformanceCounter p = new PerformanceCounter("Processor", "% Processor Time", "_Total");

   while (true)

   {

       if (p.NextValue() > level)

           System.Threading.Thread.Sleep(10);

    }

}

可以看到,上面的解法能方便地處理各種CPU使用率參數(shù)。這個程序可以解答前面提到的問題2。

有了前面的積累,我們應(yīng)該可以讓任務(wù)管理器畫出優(yōu)美的正弦曲線了,見下面的代碼。

【解法四】正弦曲線

代碼清單1-4

//C++ code  to make task manager generate sine graph

#include "Windows.h"

#include "stdlib.h"

#include "math.h"

const double SPLIT = 0.01;

const int COUNT = 200;

const double PI = 3.14159265;

const int INTERVAL = 300;

int _tmain(int argc, _TCHAR* argv[])

{

    DWORD busySpan[COUNT];  //array of busy times

    DWORD idleSpan[COUNT];  //array of idle times

    int half = INTERVAL / 2;

    double radian = 0.0;

    for(int i = 0; i < COUNT; i++)

    {

        busySpan[i] = (DWORD)(half + (sin(PI * radian) * half));

        idleSpan[i] = INTERVAL - busySpan[i];

        radian += SPLIT;

    }

    DWORD startTime = 0;       

    int j = 0;

    while (true)

    {

        j = j % COUNT;

        startTime = GetTickCount();

        while ((GetTickCount() - startTime) <= busySpan[j]) ;

        Sleep(idleSpan[j]);

        j++;

    }

    return 0;

}

討論

如果機(jī)器是多CPU,上面的程序會出現(xiàn)什么結(jié)果?如何在多個CPU時顯示同樣的狀態(tài)?例如,在雙核的機(jī)器上,如果讓一個單線程的程序死循環(huán),能讓兩個CPU的使用率達(dá)到50%的水平么?為什么?

多CPU的問題首先需要獲得系統(tǒng)的CPU信息。可以使用GetProcessorInfo()獲得多處理器的信息,然后指定進(jìn)程在哪一個處理器上運(yùn)行。其中指定運(yùn)行使用的是SetThreadAffinityMask()函數(shù)。

另外,還可以使用RDTSC指令獲取當(dāng)前CPU核心運(yùn)行周期數(shù)。

在x86平臺上定義函數(shù):

inline __int64 GetCPUTickCount()

{

     __asm

     {

         rdtsc;

     }

}

在x64平臺上定義:

#define GetCPUTickCount() __rdtsc()

使用CallNtPowerInformation API得到CPU頻率,從而將周期數(shù)轉(zhuǎn)化為毫秒數(shù),例如:

代碼清單1-5

_PROCESSOR_POWER_INFORMATION info;

CallNTPowerInformation(11,   //query processor power information

    NULL,                 //no input buffer

    0,                    //input buffer size is zero

    &info,               //output buffer

    Sizeof(info));     //outbuf size

    __int64 t_begin = GetCPUTickCount();

    //do something

    __int64 t_end = GetCPUTickCount();

    double millisec = ((double)t_end –

        (double)t_begin)/(double)info.CurrentMhz;

RDTSC指令讀取當(dāng)前CPU的周期數(shù),在多CPU系統(tǒng)中,這個周期數(shù)在不同的CPU之間基數(shù)不同,頻率也有可能不同。用從兩個不同的CPU得到的周期數(shù)作計算會得出沒有意義的值。如果線程在運(yùn)行中被調(diào)度到了不同的CPU,就會出現(xiàn)上述情況??捎肧etThreadAffinityMask避免線程遷移。另外,CPU的頻率會隨系統(tǒng)供電及負(fù)荷情況有所調(diào)整。

總結(jié)

能幫助你了解當(dāng)前線程/進(jìn)程/系統(tǒng)效能的API大致有以下這些:

1.     Sleep()——這個方法能讓當(dāng)前線程“停”下來。

2.     WaitForSingleObject()——自己停下來,等待某個事件發(fā)生

3.     GetTickCount()——有人把Tick翻譯成“嘀嗒”,很形象。

4.     QueryPerformanceFrequency()、QueryPerformanceCounter()——讓你訪問到精度更高的CPU數(shù)據(jù)。

5.     timeGetSystemTime()——是另一個得到高精度時間的方法。

6.     PerformanceCounter——效能計數(shù)器。

7.     GetProcessorInfo()/SetThreadAffinityMask()。遇到多核的問題怎么辦呢?這兩個方法能夠幫你更好地控制CPU。

8.     GetCPUTickCount()。想拿到CPU核心運(yùn)行周期數(shù)嗎?用用這個方法吧。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多