Author: Fox
前兩天在買《計(jì)算機(jī)程序設(shè)計(jì)藝術(shù)》中文版的時(shí)候,偶然發(fā)現(xiàn)《編程之美》這本書(shū),當(dāng)時(shí)翻了一下,看到“讓CPU占用率曲線聽(tīng)你指揮”這樣的題目確實(shí)讓人為之一動(dòng)。寫一段代碼,可以讓CPU占有率曲線畫(huà)出平滑的正弦曲線,有點(diǎn)意思:-)。
當(dāng)然,最后沒(méi)有買這本書(shū),雖然我可以肯定這是本好書(shū)。
我從CSDN讀書(shū)上找到幾節(jié),閑來(lái)讀一讀。今天來(lái)討論一下《讓CPU占用率曲線聽(tīng)你指揮》。
題目:寫一個(gè)程序,讓用戶來(lái)決定Windows任務(wù)管理器(Task Manager)的CPU占用率。程序越精簡(jiǎn)越好,計(jì)算機(jī)語(yǔ)言不限。例如,可以實(shí)現(xiàn)下面三種情況:
1. CPU的占用率固定在50%,為一條直線;
2. CPU的占用率為一條直線,但是具體占用率由命令行參數(shù)決定(參數(shù)范圍1~ 100);
3. CPU的占用率狀態(tài)是一個(gè)正弦曲線。
在討論具體實(shí)現(xiàn)之前,一個(gè)非常重要的問(wèn)題是:什么是CPU占用率?
《編程之美》寫道:“在任務(wù)管理器的一個(gè)刷新周期內(nèi),CPU忙(執(zhí)行應(yīng)用程序)的時(shí)間和刷新周期總時(shí)間的比率,就是CPU的占用率,也就是說(shuō),任務(wù)管理器中顯示的是每個(gè)刷新周期內(nèi)CPU占用率的統(tǒng)計(jì)平均值。”
打開(kāi)“Windows 任務(wù)管理器”,“性能”中有“CPU使用記錄”一項(xiàng),給出的就是CPU占有率曲線。
至于一個(gè)刷新周期到底是多長(zhǎng),書(shū)中似乎沒(méi)有明確給出,只是說(shuō)“大約是1秒鐘更新一次”,我打開(kāi)Windows自帶的時(shí)鐘,也感覺(jué)大約是1秒鐘。
另外的常識(shí)是:
單核環(huán)境下,空死循環(huán)會(huì)導(dǎo)致100%的CPU占有率。雙核環(huán)境下,CPU總占有率大約為50%,四核會(huì)不會(huì)是25%左右呢?(我沒(méi)有四核,只能猜測(cè)了,估計(jì)各核間切換也會(huì)耗掉點(diǎn)時(shí)間,因?yàn)槲业碾p核環(huán)境并沒(méi)有出現(xiàn)一核100%,另一核空閑的情況)。
當(dāng)CPU整個(gè)刷新周期(絕大多數(shù)時(shí)間)空閑時(shí),CPU占有率趨于0。
書(shū)中給出的正弦實(shí)現(xiàn)如下:
1 #include "Windows.h"
2 #include "stdlib.h"
3 #include "math.h"
4
5 const double SPLIT = 0.01;
6 const int COUNT = 200;
7 const double PI = 3.14159265;
8 const int INTERVAL = 300;
9
10 int _tmain(int argc, _TCHAR* argv[])
11 {
12 DWORD busySpan[COUNT]; //array of busy times
13 DWORD idleSpan[COUNT]; //array of idle times
14 int half = INTERVAL / 2;
15 double radian = 0.0;
16 for(int i = 0; i < COUNT; i++)
17 {
18 busySpan[i] = (DWORD)(half + (sin(PI * radian) * half));
19 idleSpan[i] = INTERVAL - busySpan[i];
20 radian += SPLIT;
21 }
22 DWORD startTime = 0;
23 int j = 0;
24 while (true)
25 {
26 j = j % COUNT;
27 startTime = GetTickCount();
28 while ((GetTickCount() - startTime) <= busySpan[j]) ;
29 Sleep(idleSpan[j]);
30 j++;
31 }
32 return 0;
33 }
在單核環(huán)境(P4 2.40)下,其表現(xiàn)還是不錯(cuò)的:
點(diǎn)擊查看大圖
在雙核環(huán)境(Core2 E4500)下,就有點(diǎn)差強(qiáng)人意不盡人意了:
點(diǎn)擊查看大圖
不過(guò),總還能看出是正弦曲線。
上面兩圖的問(wèn)題:
1) 單核時(shí)曲線不夠平滑,是由于QQ等程序占用CPU所致;
2) 雙核時(shí)曲線更加抖動(dòng),我的理解是除其他程序影響外,由于線程沒(méi)有固定運(yùn)行在一個(gè)CPU上導(dǎo)致的,后面看到書(shū)上提到線程遷移,個(gè)人感覺(jué)這個(gè)叫法欠妥啊,總覺(jué)得線程遷移令人費(fèi)解。
可以立即想到的是:讓進(jìn)程在指定處理器上運(yùn)行(處理器親緣關(guān)系),由Windows提供了兩個(gè)API可以做到這一點(diǎn):GetCurrentProcess和SetProcessAffinityMask的。
修改之后的代碼如下:
1 #include "Windows.h"
2 #include "stdlib.h"
3 #include "math.h"
4
5 const double SPLIT = 0.01;
6 const int COUNT = 200;
7 const double PI = 3.14159265;
8 const int INTERVAL = 300;
9
10 int _tmain(int argc, _TCHAR* argv[])
11 {
12 SetProcessAffinityMask(
13 GetCurrentProcess(),
14 0x00000001 //cpu mask
15 );
16
17 DWORD busySpan[COUNT]; //array of busy times
18 DWORD idleSpan[COUNT]; //array of idle times
19 int half = INTERVAL / 2;
20 double radian = 0.0;
21 for(int i = 0; i < COUNT; i++)
22 {
23 busySpan[i] = (DWORD)(half + (sin(PI * radian) * half));
24 idleSpan[i] = INTERVAL - busySpan[i];
25 radian += SPLIT;
26 }
27 DWORD startTime = 0;
28 int j = 0;
29 while (true)
30 {
31 j = j % COUNT;
32 startTime = GetTickCount();
33 while ((GetTickCount() - startTime) <= busySpan[j]) ;
34 Sleep(idleSpan[j]);
35 j++;
36 }
37 return 0;
38 }
雙核環(huán)境(Core2 E4500)修改之后的輸出如下:
點(diǎn)擊查看大圖
我理想中的表現(xiàn)是:
1) 曲線是平滑的,最好不因其他應(yīng)用程序或操作的執(zhí)行而改變;
2) 不管是單核還是雙核,峰值皆為100%,谷值為0。
對(duì)于第一點(diǎn),其實(shí)就是保證任一刷新周期中的CPU占有率都可以被精確控制在0-100之間,如果你可以使CPU一直保持50%(而不是近似的上下波動(dòng)),產(chǎn)生一條平滑的曲線就很easy了。
問(wèn)題的關(guān)鍵在于,除了當(dāng)前你寫的程序可以控制,其他程序或操作如何控制?或者說(shuō):如何控制CPU的運(yùn)行情況才是關(guān)鍵之處。
PS: 一晚上老是斷網(wǎng),搞得思路頻頻被打斷,興致也損了大半??傊?a target="_blank">編程之美》還是值得玩味一把吧:D。