CPU曲線

轉自:
http://blog.csdn.net/skyline0623/article/details/6576072

讓多核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(),0x00000001);可以指定當前執行的進程在第一個CPU上運行。對於雙核CPU,
::SetProcessAffinityMask(::GetCurrentProcess(),0x00000002);可以指定在第二個CPU上運行。
::SetProcessAffinityMask(::GetCurrentProcess(),0x00000003);可以允許在兩個CPU上任意運行。
HANDLE GetCurrentProcess(void);
可以獲得當前進程的句柄。注意,這個句柄爲一個僞句柄。只能在我們的進程中才能代表當前進程的句柄,事實上這個函數目前只是簡單的返回-1這個值。也就是說在我們的程序中-1便能表示本進程的句柄。
(2)那麼對於繪製50%直線,程序代碼爲:
  1. #include <Windows.h>
  2. #include<stdlib.h>
  3. #include<tchar.h>
  4. int _tmain(int argc,_TCHAR* argv[])
  5. {
  6. int busyTime = 10;
  7. int idleTime = busyTime*5;
  8. __int64 startTime = 0;
  9. ::SetThreadAffinityMask(::GetCurrentProcess(),0x00000001);
  10. while(true)
  11. {
  12. startTime = GetTickCount();
  13. //busy loop
  14. while((GetTickCount() - startTime) <= busyTime);
  15. //idle loop
  16. Sleep(idleTime);
  17. }
  18. return 0;
  19. }
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)對於繪製正弦曲線:
  1. #include <Windows.h>
  2. #include<stdlib.h>
  3. #include<math.h>
  4. #include<tchar.h>
  5. const double SPLIT = 0.01;
  6. const int COUNT = 200;
  7. const double PI = 3.14159265;
  8. const int INTERVAL = 300;
  9. int _tmain(int argc, _TCHAR* argv[] )
  10. {
  11. DWORD busySpan[COUNT]; //array of busy times
  12. DWORD idleSpan[COUNT]; //array of idle times
  13. int half = INTERVAL/2;
  14. double radian = 0.0;
  15. //如何近似趨近一條正弦曲線?這樣!
  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. ::SetProcessAffinityMask(::GetCurrentProcess(),0x00000002);
  25. while(true)
  26. {
  27. j = j % COUNT;
  28. startTime = GetTickCount();
  29. while((GetTickCount() - startTime) <= busySpan[j]);
  30. Sleep(idleSpan[j]);
  31. j++;
  32. }
  33. return 0;
  34. }
通過在一個週期2*PI中等分200份,將每一個間隔點的half + (sin( PI * radian) * half))的值存入busySpan[i],將其補植存入idleSpan[i]。half是整個值域INTERVAL的一半。這樣可以近似趨近一條正弦曲線。
運行效果爲:
(4)可以通過RDTSC指令獲得當前CPU核心運行週期數。
在x86平臺上定義函數:
  1. inline __int64 GetCPUTickCount()
  2. {
  3. __asm
  4. {
  5. rdtsc;
  6. }
  7. }
在x64平臺上定義:
#define GetCPUTickCount() __rdtsc()
使用CallNtPowerInformation API得到CPU頻率,從而將週期數轉化爲毫秒數,例如如下:
  1. _PROCESSOR_POWER_INFORMATION info;
  2. CallNTPowerInformation(11, //query processor power information
  3. NULL, //no input buffer
  4. 0, //input buffer size is zero
  5. &info, //output buffer
  6. sizeof(info) //outbuf size
  7. );
  8. __int64 t_begin = GetCPUTickCount();
  9. //do something
  10. __int64 t_end = GetCPUTickCount();
  11. millisec = ((double)t_end - (double)t_begin)/(double)info.CurrentMhz;
RDTSC指令讀取當前CPU的週期數,在多CPU系統中這個週期數在不同的CPU間基數不同,頻率也不同。用從兩個不同的CPU得到的週期數來計算會得出沒有意義的值。所以需要用SetProcessAffinityMask避免進程遷移。另外,CPU的頻率也會隨系統供電及負荷情況有所調整。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章