CPU正弦曲線


 1 #include <iostream>
 2 #include <cmath>
 3 #include <ctime>
 4 #include <windows.h>
 5 
 6 using namespace std;
 7 
 8 //得到循環0xFFFFFFFF次用的秒數
 9 unsigned int test()
10 {
11     unsigned int c = 0xFFFFFFFF;
12 
13     time_t t1, t2;
14     time(&t1);
15 
16     for(unsigned int i = 0; i < c; i++)
17         ;
18     time(&t2);
19     return (unsigned int)(t2 -t1);
20 
21 }
22 
23 
24 #define T  20000                                        //周期時間 20秒
25 #define C  100                                            //采樣點時間間隔
26 #define PI 3.1415                                        //PI
27 const unsigned int count = 0xFFFFFFFF / (test() *1000); //采樣間隔可以執行的循環數目
28 const unsigned int N = T/C;                                //周期內采樣點數目
29 unsigned int v[N] = { 0 };                                //所有采樣點連續執行循環數
30 unsigned int mt[N] = { 0 };                                //所有采樣點休眠毫秒數
31 
32 int main()
33 {
34     //計算循環次數和休眠時間
35     for(int i = 0; i < N; i++)
36     {
37         double x = 2 * PI * i / N;
38         double r = (sin(x) + 1) / 2;
39         
40         mt[i] = C - r * C;
41         v[i] = r * C * count;
42     }
43     for(;;)
44     {
45         for(int i = 0; i < N; i++)
46         {
47             for(int j = 0; j < v[i]; j++)
48                 ;
49             Sleep(mt[i]);
50         }
51     }
52 }

 


 

首先什么是CPU占用率?

在任務管理器的一個刷新周期內,CPU忙(執行應用程序)的時間和刷新周期總時間的比率,就是CPU的占用率,也就是說,任務管理器中顯示的是每個刷新周期內CPU占用率的統計平均值。

因此可以寫個程序,在一個刷新周期中,一會兒忙,一會兒閑,調節忙/閑比例,就可以控制CPU占有率!

一個刷新時間到底是多長,書上只是說,任務管理器觀測,大約是1秒。鼠標移動、后台程序等都會對曲線造成影響!

單核環境下,空死循環會導致100%的CPU占有率。雙核環境下,CPU總占有率大約為50%,四核會不會是25%左右呢?(沒試過)


 

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

 

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

 

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

 

3.    CPU的占用率狀態是一個正弦曲線。

 


 

解法一:簡單解法

 

Busy用可循環來實現,for(i=0;i<n;i++) ;

 

對應的匯編語言為

 

loop;

 

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

 

inc dx          ;將dx寄存器加1

 

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

 

cmp i n         ;比較i和n

 

j1 loop          ;i小於n時則重復循環

 

我的cpu(雙核) 2.53GHZ 現代cpu每個時鍾周期可執行兩條以上的代碼,取平均值2,於是

 

(2520 000 000*2)/5=1012000000(循環/秒) 每秒可以執行循環1012000000次

 

不能簡單的取n=10120000000然后sleep(1000),如果讓cpu工作1s,休息1s很可能是鋸齒,先達到一個峰值然后跌入一個很低的占有率;所以我們睡眠時間改為10ms,10ms比較接近windows的調度時間,n=10120000。如果sleep時間選的太小,會造成線程頻繁的喚醒和掛起,無形中增加了內核時間的不確定性因此代碼如下:

 1 #include <windows.h>
 2 
 3 int main(void)
 4 {
 5     //50%
 6     //Thread 0 can only run on CPU 0.
 7     SetThreadAffinityMask(GetCurrentProcess(), 0x00000001);
 8     while(true)
 9     {
10         for(int i=0;i<10120000;i++)
11             ;
12         Sleep(10);
13     }
14     return 0;
15     
16 
17 }

 

解法二:使用GetTickCount()和Sleep()

 

GetTickCount()可以得到“系統啟動到現在”所經歷的時間的毫秒值,最多可以統計49.7天,因此我們可以利用GetTickCount()判斷busy loop要循環多久,如下:

 

 

 1 #include <windows.h>
 2 
 3 int main(void)
 4 {
 5         
 6     //50%
 7     int busyTime=10;
 8     int idleTime=busyTime;
 9     _int64 startTime;
10          SetThreadAffinityMask(GetCurrentProcess(), 0x00000001);
11     while(true)
12     {
13         startTime=GetTickCount();
14         while((GetTickCount()-startTime)<=busyTime)
15         {
16             ;
17         }
18         Sleep(idleTime);
19     }
20     return 0;
21 }

 

上面兩種解法都是假設當前系統只有當前程序在運行,但實際上,操作系統有很多程序會同時調試執行各種任務,如果此刻進程使用10%的cpu,那我們的程序只有使用40%的cpu才能達到50%的效果。

 

Perfmon.exe是從WIN NT開始就包含在windows管理工具中的專業檢測工具之一。我們可以用程序來查詢Perfmon的值,.Net Framework提供了PerformanceCounter這一對象,可以方便的查詢當前各種性能數據,包括cpu使用率,因此解法三如下:

 

解法三:能動態適應的解法

 1 using System;
 2 using System.Diagnostics;
 3 namespace cpu
 4 {
 5     class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9            cpu(0.5);
10         }
11         static void cpu(double level)
12         {
13             PerformanceCounter p = new PerformanceCounter("Processor", "% Processor Time", "_Total");
14             if (p == null)
15             {
16                 return;
17             }
18             while (true)
19             {
20                 if (p.NextValue() > level)
21                     System.Threading.Thread.Sleep(10);
22             }
23         }
24     }
25 }

 

解法四:正弦曲線

 

 1 #include <windows.h>
 2 #include <math.h>
 3 int main(void)
 4 {
 5          SetThreadAffinityMask(GetCurrentProcess(), 0x00000001);
 6     const double SPLIT=0.01;
 7     const int COUNT=200;
 8     const double PI=3.14159265;
 9     const int INTERVAL=300;
10     DWORD busySpan[COUNT]; //array of busy time
11     DWORD idleSpan[COUNT]; //array of idle time
12     int half=INTERVAL/2;
13     double radian=0.0;
14     for(int i=0;i<COUNT;i++)
15     {
16         busySpan[i]=(DWORD)(half+(sin(PI*radian)*half));
17         idleSpan[i]=INTERVAL-busySpan[i];
18         radian+=SPLIT;
19     }
20     DWORD startTime=0;
21     int j=0;
22     while(true)
23     {
24         j=j%COUNT;
25         startTime=GetTickCount();
26         while((GetTickCount()-startTime)<=busySpan[j])
27             ;
28         Sleep(idleSpan[j]);
29         j++;
30     }
31     return 0;
32 }

 

其中busySpan[i]=(DWORD)(half+(sin(PI*radian)*half));   idleSpan[i]=INTERVAL-busySpan[i];

這樣保證了占有率=busy/(busy+idle)=(half+(sin(PI*radian)*half))/(2*half)=(1+sin(PI*radian))/2 在0到100%之間!


面更深入在雙核情況下,每個cpu顯示不同的曲線。如下:

 

控制CPU占用率,不僅僅是出於好玩而已。以前的某些程序,特別是某些老游戲,在新的機器上運行速度太快,必須先給CPU降速,才能順利運行那些程序,有個共享軟件CPUKiller,就是專門弄這個的。

 

控制CPU占用率,因為要調用Windows的API,要考慮到多核、超線程的情況,要考慮到不同版本的Windows的計時相關的API的精度不同,使問題變得相當復雜,若再考慮其它程序的CPU占用率,則該問題則變得很煩人。(taskmgr調用了一個未公開的API)。

 

對CPU核數的判斷,書上是調用GetProcessorInfo,其實可以直接調用GetSystemInfo,SYSTEM_INFO結構的dwNumberOfProcessors成員就是核數。不知道超線程對這兩種方法有什么影響。

 

如果不考慮其它程序的CPU占用情況,可以在每個核上開一個線程,運行指定的函數,實現每個核的CPU占用率相同

 

要讓CPU的占用率,呈函數 y = calc(t) (0 <= y <= 1, t為時間,單位為ms )分布,只要取間隔很短的一系列點,認為在某個間隔內,y值近似不變。

 

設間隔值為GAP,顯然在指定t值附近的GAP這段時間內,

 

CPU占用時間為:busy = GAP * calc(t),

 

CPU空閑時間為:idle = GAP - busy

 

因此,很容易寫出下面這個通用函數:

 1 void solve(Func *calc)
 2 {
 3   double tb = 0;
 4   while(1) 
 5  {
 6     unsigned ta = get_time();
 7     double r = calc(tb);
 8     if (r < 0 || r > 1) r = 1;
 9     DWORD busy = r * GAP;
10     while(get_time() - ta < busy) {}
11     Sleep(GAP - busy);
12     tb += GAP;
13   }
14 }

如果CPU占用率曲線不是周期性變化,就要對每個t值都要計算一次,否則,可以只計算第一個周期內的各個t值,其它周期的直接取緩存計算結果。

以CPU占用率為正弦曲線為例,顯然:y = 0.5 * (1 + sin(a * t + b))

其周期T = 2 * PI / a  (PI = 3.1415927),可以指定T值為60s即60000ms,則

可以確定a值為 2 * PI / T, 若在這60000ms內我們計算200次(c = 200),則GAP值為 T / c = 300ms.也就是說,只要確定了周期和計算次數,其它幾個參數也都確定下來。

代碼如下:

  1 #include<iostream>
  2 #include<cmath>
  3 #include<windows.h>
  4 
  5 static int PERIOD = 60 * 1000; //周期ms
  6 const int COUNT = 300;  //一個周期計算次數
  7 const double GAP_LINEAR = 100;  //線性函數時間間隔100ms
  8 const double PI = 3.1415926535898; //PI
  9 const double GAP = (double)PERIOD / COUNT; //周期函數時間間隔
 10 const double FACTOR = 2 * PI / PERIOD;  //周期函數的系數
 11 static double Ratio = 0.5;  //線性函數的值 0.5即50%
 12 static double Max=0.9; //方波函數的最大值
 13 static double Min=0.1; //方波函數的最小值
 14 
 15 typedef double Func(double);  //定義一個函數類型 Func*為函數指針
 16 typedef void Solve(Func *calc);//定義函數類型,參數為函數指針Func*
 17 inline DWORD get_time() 
 18 { 
 19     return GetTickCount(); //操作系統啟動到現在所經過的時間ms
 20 }
 21 double calc_sin(double x)  //調用周期函數solve_period的參數
 22 {  
 23     return (1 + sin(FACTOR * x)) / 2; //y=1/2(1+sin(a*x))
 24 }
 25 double calc_fangbo(double x)  //調用周期函數solve_period的參數
 26 {
 27     //方波函數
 28     if(x<=PERIOD/2) return Max;
 29     else return Min;
 30 }
 31 
 32 void solve_period(Func *calc) //線程函數為周期函數
 33 {
 34     double x = 0.0;
 35     double cache[COUNT];
 36     for (int i = 0; i < COUNT; ++i, x += GAP) 
 37         cache[i] = calc(x); 
 38     int count = 0;
 39     while(1)
 40     {
 41         unsigned ta = get_time();
 42         if (count >= COUNT) count = 0;
 43         double r = cache[count++];
 44         DWORD busy = r * GAP;
 45         while(get_time() - ta < busy) {}
 46         Sleep(GAP - busy);
 47   }
 48 }
 49 
 50 void solve_linear(Func*)  //線程函數為線性函數,參數為空 NULL
 51 {
 52     const unsigned BUSY =  Ratio * GAP_LINEAR;
 53     const unsigned IDLE = (1 - Ratio) * GAP_LINEAR;
 54     while(1)
 55     {
 56         unsigned ta = get_time();
 57         while(get_time() - ta < BUSY) {}
 58         Sleep(IDLE);
 59     }
 60 }
 61 //void solve_nonperiod(Func *calc) //非周期函數的處理,暫沒實驗
 62 //{
 63 //  double tb = 0;
 64 //  while(1)
 65 //  {
 66 //    unsigned ta = get_time();
 67 //    double r = calc(tb);
 68 //    if (r < 0 || r > 1) r = 1;
 69 //    DWORD busy = r * GAP;
 70 //    while(get_time() - ta < busy) {}
 71 //    Sleep(GAP - busy);
 72 //    //tb += GAP;
 73 //    tb += get_time() - ta;
 74 //  }
 75 //}
 76 
 77 void run(int i=1,double R=0.5,double T=60000,double max=0.9,double min=0.1)
 78      //i為輸出狀態,R為直線函數的值,T為周期函數的周期,max方波最大值,min方波最小值
 79 {
 80     Ratio=R; PERIOD=T; Max=max; Min=min;
 81     Func *func[] = {NULL ,calc_sin,calc_fangbo};  //傳給Solve的參數,函數指針數組
 82     Solve *solve_func[] = { solve_linear, solve_period};  //Solve函數指針數組
 83     const int NUM_CPUS = 2;  //雙核,通用的可以用下面GetSystemInfo得到cpu數目
 84     HANDLE handle[NUM_CPUS];  
 85     DWORD thread_id[NUM_CPUS]; //線程id
 86     //SYSTEM_INFO info;
 87     //GetSystemInfo(&info);   //得到cpu數目
 88     //const int num = info.dwNumberOfProcessors;
 89     switch(i)
 90     {
 91     case 1: //cpu1 ,cpu2都輸出直線
 92         {
 93             for (int i = 0; i < NUM_CPUS; ++i)
 94             {
 95                 Func *calc = func[0];
 96                 Solve *solve = solve_func[0];
 97                 if ((handle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve, 
 98                                     (VOID*)calc, 0, &thread_id[i])) != NULL)  //創建新線程
 99                 SetThreadAffinityMask(handle[i], i+1); //限定線程運行在哪個cpu上
100             }
101             WaitForSingleObject(handle[0],INFINITE);   //等待線程結束
102             break;
103         }
104     case 2: //cpu1直線,cpu2正弦
105         {
106             for (int i = 0; i < NUM_CPUS; ++i)
107             {
108                 Func *calc = func[i];
109                 Solve *solve = solve_func[i];
110                 if ((handle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve, 
111                                     (VOID*)calc, 0, &thread_id[i])) != NULL)  //創建新線程
112                 SetThreadAffinityMask(handle[i], i+1); //限定線程運行在哪個cpu上
113             }
114             WaitForSingleObject(handle[0],INFINITE);   //等待線程結束
115             break;
116         }
117         case 3: //cpu1直線,cpu2方波
118         {
119 
120             /*Func *calc = func[0];
121             Solve *solve = solve_func[0];*/
122             if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[0], 
123                                 (VOID*)func[0], 0, &thread_id[0])) != NULL)  //創建新線程
124             SetThreadAffinityMask(handle[0], 1); //限定線程運行在哪個cpu上
125             Func *calc = func[2];
126             Solve *solve = solve_func[1];
127             if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve, 
128                                 (VOID*)calc, 0, &thread_id[1])) != NULL)  //創建新線程
129             SetThreadAffinityMask(handle[1], 2); //限定線程運行在哪個cpu上
130             WaitForSingleObject(handle[0],INFINITE);   //等待線程結束
131             break;
132         }
133         case 4: //cpu1正弦,cpu2方波
134         {
135             if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[1], 
136                                 (VOID*)func[1], 0, &thread_id[0])) != NULL)  //創建新線程
137             SetThreadAffinityMask(handle[0], 1); //限定線程運行在哪個cpu上
138             Func *calc = func[2];
139             Solve *solve = solve_func[1];
140             if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve, 
141                                 (VOID*)calc, 0, &thread_id[1])) != NULL)  //創建新線程
142             SetThreadAffinityMask(handle[1], 2); //限定線程運行在哪個cpu上
143             WaitForSingleObject(handle[0],INFINITE);   //等待線程結束
144             break;
145         }
146         default: break;
147     }
148 }
149 
150 void main()
151 {
152     run(1,0.5);  //cpu1 ,cpu2都輸出50%的直線
153     //run(2,0.5,30000); //cpu1 0.5直線,cpu2正弦周期30000
154     //run(3);  //cpu1直線,cpu2方波
155     //run(4,0.8,30000,0.95,0.5); //cpu1正弦,cpu2 0.95-0.5的方波
156 }

補充幾個函數的說明:

GetTickCount返回(retrieve)從操作系統啟動到現在所經過(elapsed)的毫秒數,它的返回值是DWORD。函數原型:   DWORD GetTickCount(void); C/C++頭文件:winbase.h ;windows程序設計中可以使用頭文件windows.h

Sleep()函數 C++中頭文件<windows.h>下的函數 作用:延時,程序暫停若干時間。時間,就是他的參數,單位是毫秒。Sleep (500) ; //注意第一個字母是大寫。就是到這里停半秒,然后繼續向下執行。  在Linux C語言中 sleep的單位是秒 sleep(5); //停5秒 包含在 <unistd.h>頭文件中

CreateThread,建立新的線程 HANDLE CreateThread(  

LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes  

DWORD dwStackSize,                         // initial thread stack size  

LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread function  

LPVOID lpParameter,                        // argument for new thread  

DWORD dwCreationFlags,                     // creation flags  

LPDWORD lpThreadId                         // pointer to receive thread ID );

概述: 當使用CreateProcess調用時,系統將創建一個進程和一個主線程。CreateThread將在主線程的基礎上創建一個新線程,大致做如下步驟: 1在內核對象中分配一個線程標識/句柄,可供管理,由CreateThread返回 2把線程退出碼置為STILL_ACTIVE,把線程掛起計數置1 3分配context結構 4分配兩頁的物理存儲以准備棧,保護頁設置為PAGE_READWRITE,第2頁設為PAGE_GUARD 5lpStartAddr和lpvThread值被放在棧頂,使它們成為傳送給StartOfThread的參數 6把context結構的棧指針指向棧頂(第5步)指令指針指向startOfThread函數 語法: hThread = CreateThread (&security_attributes,dwStackSize,ThreadProc,pParam,dwFlags, &idThread) ; 參數說明:       第一個參數是指向SECURITY_ATTRIBUTES型態的結構的指針。在Windows 98中忽略該參數。在Windows NT中,它被設為NULL。第二個參數是用於新線程的初始堆棧大小,默認值為0。在任何情況下,Windows根據需要動態延長堆棧的大小。   CreateThread的第三個參數是指向線程函數的指針。函數名稱沒有限制,但是必須以下列形式聲明: DWORD WINAPI ThreadProc (PVOID pParam) ;  CreateThread的第四個參數為傳遞給ThreadProc的參數。這樣主線程和從屬線程就可以共享數據。   CreateThread的第五個參數通常為0,但當建立的線程不馬上執行時為旗標CREATE_SUSPENDED。線程將暫停直到呼叫ResumeThread來恢復線程的執行為止。第六個參數是一個指標,指向接受執行緒ID值的變量。

SetThreadAffinityMask:The SetThreadAffinityMask function sets a processor affinity mask for the specified thread. DWORD_PTR SetThreadAffinityMask(HANDLE hThread, DWORD_PTR dwThreadAffinityMask); 調用SetThreadAffinityMask,能為各個線程設置親緣性屏蔽:該函數中的h T h r e a d參數用於指明要限制哪個線程, dwThreadAffinityMask用於指明該線程能夠在哪個CPU上運行。dwThreadAffinityMask必須是進程的親緣性屏蔽的相應子集。返回值是線程的前一個親緣性屏蔽。因此,若要將3個線程限制到CPU1、2和3上去運行,可以這樣操作: //Thread 0 can only run on CPU 0. SetThreadAffinityMask(hThread0, 0x00000001); //第0位是1 //Threads 1, 2, 3 run on CPUs 1, 2, 3.//第1 2 3位是1 SetThreadAffinityMask(hThread1, 0x0000000E); SetThreadAffinityMask(hThread2, 0x0000000E); SetThreadAffinityMask(hThread3, 0x0000000E);

WaitForSingleObject   當指定的對象的狀態被標記或者指定的時間間隔過完時,此函數返回DWORD類型參數。

格式:DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

參數:hHandle表示對象的句柄,dwMilliseconds指出了時間間隔;過了指定的時間,即使對象狀態沒發生改變,函數也會返回;如果此參數設為0,函數測試對象的狀態並且立即返回;如果此參數設為INFINITE,則表示此函數的時間間隔永遠不會流逝完——只有等待對象狀態被標識時返回。 返回值:成功:WAIT_OBJECT_0:表示對象的狀態被標識          WAIT_TIMEOUT:表示指定時間已到而對象狀態沒有被標識     失敗:WAIT_FAILED:表明失敗 WaitForSingleObject 函數檢查指定對象當前狀態,如果對象的狀態沒有被標識,則調用的線程進入有效的等待狀態。在等待對象狀態被標識或者指定的時間間隔到期,線程只會占據(consume)處理器一小段時間。時間間隔需要被指定在0到0x7FFFFFFF之間的正數,最大的時間間隔值不等於無窮大而是0x7FFFFFFF,無窮大的時間間隔值是0xFFFFFFFF。任何在0x7FFFFFFF和0xFFFFFFFE之間的值都等同於0x7FFFFFFF;如果你需要一個時間間隔比0x7FFFFFFF還要大的話,使用表示不窮的值0xFFFFFFFF。返回之前,等待函數修改了某些類型的同步對象的狀態,只有當對象的信號狀態引起了函數的返回時這種修改才發生。例如,一個信號量對象計數減少1。WaitForSingleObject 函數能等待如下的各種對象:事件(Event)、線程(Thread)、進程(Process)、互斥量(Mutex)、信號量(Semaphore)。 使用時要小心調用等待函數和直接或間接產生窗口的代碼。如果一個線程創建了窗口,那么它必須處理消息。廣播消息發送到系統中的所有窗口。使用一個沒有時間間隔的等待函數的線程可能導致系統死鎖。例如,動態數據交換(DDE)協議和COM函數CoInitialize兩個都間接地創建了可能導致死鎖的窗口。因此,如果您有一個線程創建的窗口,使用MsgWaitForMultipleObjects 或者 MsgWaitForMultipleObjectsEx 而不是使用WaitForSingleObject。

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM