讓多核CPU佔用率曲線聽你指揮——《編程之美》1.1學習筆記Problem:
寫一個程式,讓使用者來決定Windows工作管理員(Task Manager)的CPU佔用率。有以下幾種情況:
1.CPU佔用率固定在50%,為一條直線;
2.CPU的佔用率為一條直線,具體佔用率由命令列參數決定(範圍1~100);
3.CPU的佔用率狀態為一條正弦曲線。
分析與解法:
(1)通過觀察工作管理員,它大約1s更新一次。當CPU使用率為0時,System Idle Process佔用了CPU的空閑時間。
System Idle Process在CPU閒置的時候,發出一個IDLE命令,使CPU掛起(暫時停止工作),可有效降低CPU核心的溫度,無法終止。在這個進程裡出現的CPU佔用數值並不是真正的佔用而是體現的CPU的空閑率,也就說這個數值越大CPU的空閑率就越高,反之就是CPU的佔用率越高。Linux中對應的進程為init,PID為1。
當系統中的進程或者在等待使用者輸入,或者在等待某些事件的發生(發出I/O請求等待I/O響應),或者主動進入休眠狀態(比如Sleep())。
在工作管理員中的一個重新整理周期內,CPU忙(執行應用程式)的時間和重新整理周期總時間的比率就是CPU的佔用率。其顯示的是每個重新整理周期內CPU佔用率的統計平均值。我們可以寫一個程式讓它在工作管理員的重新整理時間內一會兒忙,一會兒閑,通過調節忙/閑的比例,來控制工作管理員中顯示的CPU佔用率。
書上的代碼以單核CPU為前提,但對於多核CPU來說,同一個進程可能被CPU的任務分配器分配到不同的核心上執行,所以造成無法讓工作管理員達到預想的效果。其實開啟工作管理員,可以看到多個CPU使用記錄。本人電腦CPU是Core i5 450M,雙核4線程。在OS看來就如同有四個CPU工作一樣。我的工作管理器中就有四個CPU使用記錄。
所謂超執行緒技術就是利用特殊的硬體指令,把多執行緒器內部的兩個邏輯核心類比成兩個物理晶片,從而使單個處理器就能“享用”線程級的並行計算的處理器技術。多線程技術可以在支援多線程的作業系統和軟體上,有效增強處理器在多任務、多執行緒上的處理能力。
可以使用SetProcessAffinityMask()函數可以使特定的處理器運行指定進程。
BOOL SetProcessAffinityMask(HANDLE hProcess, DWORD_PTR dwProcessAffinityMask);
第一個參數用來指定指定哪個進程,傳入它的控制代碼。第二個進程用來指定哪個CPU核心來執行此進程。
DWORD_PTR,其實就是unsigned long*.Unsigned long type for pointer precision.Use when casting a pointer to a long type to perform pointer arithmetic.(Also commonly used for general 32-bit parameters that have been extended to 64 bits in 64-bit windows.)
DWORD 其實就是unsigned long。Windows下常用來儲存地址或存放指標。
比如這樣調用函數:
::SetProcessAffinityMask(::GetCurrentProcess(),0x1);可以指定當前執行的進程在第一個CPU上運行(00000001)。對於雙核CPU,
::SetProcessAffinityMask(::GetCurrentProcess(),0x2);可以指定在第二個CPU上運行。(00000010)
::SetProcessAffinityMask(::GetCurrentProcess(),0x3);可以允許在兩個CPU上任意運行。(000000011)
::SetProcessAffinityMask(::GetCurrentProcess(),0x3);可以允許在第三個CPU上任意運行。(000000100)
::SetProcessAffinityMask(::GetCurrentProcess(),0x3);可以允許在第一個和第三個CPU上任意運行。(00000101)
以此類推。。。
HANDLE GetCurrentProcess(void);
可以獲得當前進程的控制代碼。注意,這個控制代碼為一個偽控制代碼。只能在我們的進程中才能代表當前進程的控制代碼,事實上這個函數目前只是簡單的返回-1這個值。也就是說在我們的程式中-1便能表示本進程的控制代碼。
(2)那麼對於繪製50%直線,程式碼為:
#include <Windows.h><br />#include<stdlib.h><br />#include<tchar.h><br />int _tmain(int argc,_TCHAR* argv[])<br />{<br />int busyTime = 10;<br />int idleTime = busyTime*5;<br />__int64 startTime = 0;<br />::SetThreadAffinityMask(::GetCurrentProcess(),0x00000001);<br />while(true)<br />{<br />startTime = GetTickCount();<br /> //busy loop<br />while((GetTickCount() - startTime) <= busyTime);<br /> //idle loop<br /> Sleep(idleTime);<br />}<br />return 0;<br />}
GetTickCount()可以得到系統從啟動到運行到現在所經曆時間的毫秒值。最多能統計到49.7天。我們利用它判斷busy loop要持續多久。
其中idleTime為busyTime的五倍,可以修改其值使其更逼近50%。不同機子的情況不同。
__int64是VC++的64位擴充。範圍為[-2^63,2^63)。當64位與32位混合運算時,32位整數會隱式轉換成64位整數。輸入輸出它時使用cin、cout會造成錯誤。需要使用scanf("%I64d",&a);和printf("%I64d",a);
還有unsigned __int64,其範圍為[0,2^64)。
對應g++中的64位擴充為long long和unsigned long long。範圍與運算與上相仿。輸入輸出使用scanf("%lld",&a);和printf("%lld",a);
int _tmain(int argc, _TCHAR* argv[])。
_tmain這個符號多見於VC++建立的控制台工程中,這個是為了保證移植unicode而加入的(一般_t、_T、T()這些東西都和unicode有關係)。定義在標頭檔tchar.h中。
(3)對於繪製正弦曲線:
#include <Windows.h><br />#include<stdlib.h><br />#include<math.h><br />#include<tchar.h><br />const double SPLIT = 0.01;<br />const int COUNT = 200;<br />const double PI = 3.14159265;<br />const int INTERVAL = 300;<br />int _tmain(int argc, _TCHAR* argv[] )<br />{<br />DWORD busySpan[COUNT]; //array of busy times<br />DWORD idleSpan[COUNT]; //array of idle times<br />int half = INTERVAL/2;<br />double radian = 0.0;<br /> //如何近似趨近一條正弦曲線?這樣!<br />for(int i = 0; i < COUNT; ++i)<br />{<br />busySpan[i] = (DWORD)(half + (sin(PI * radian) * half));<br />idleSpan[i] = INTERVAL - busySpan[i];<br />radian += SPLIT;<br />}<br />DWORD startTime = 0;<br />int j = 0;<br />::SetProcessAffinityMask(::GetCurrentProcess(),0x00000002);<br />while(true)<br />{<br />j = j % COUNT;<br />startTime = GetTickCount();<br />while((GetTickCount() - startTime) <= busySpan[j]);<br />Sleep(idleSpan[j]);<br />j++;<br />}<br />return 0;<br />}
通過在一個周期2*PI中等分200份,將每一個間隔點的half + (sin( PI * radian) * half))的值存入busySpan[i],將其補植存入idleSpan[i]。half是整個範圍INTERVAL的一半。這樣可以近似趨近一條正弦曲線。
運行效果為:
(4)可以通過RDTSC指令獲得當前CPU核心運行周期數。
在x86平台上定義函數:
inline __int64 GetCPUTickCount()<br />{<br />__asm<br />{<br />rdtsc;<br />}<br />}
在x64平台上定義:
#define GetCPUTickCount() __rdtsc()
使用CallNtPowerInformation API得到CPU頻率,從而將周期數轉化為毫秒數,例如如下:
_PROCESSOR_POWER_INFORMATION info;<br />CallNTPowerInformation(11,//query processor power information<br />NULL,//no input buffer<br />0,//input buffer size is zero<br />&info,//output buffer<br />sizeof(info)//outbuf size<br />);<br />__int64 t_begin = GetCPUTickCount();<br />//do something<br />__int64 t_end = GetCPUTickCount();<br />millisec = ((double)t_end - (double)t_begin)/(double)info.CurrentMhz;
RDTSC指令讀取當前CPU的周期數,在多CPU系統中這個周期數在不同的CPU間基數不同,頻率也不同。用從兩個不同的CPU得到的周期數來計算會得出沒有意義的值。所以需要用SetProcessAffinityMask避免進程遷移。另外,CPU的頻率也會隨系統供電及負荷情況有所調整。
More:
如何在Linux下如Ubuntu中的系統監視器中實現繪製正弦曲線?請聽下回分解。