Linux下實現CPU使用率正弦曲線

 編程之美第一道題目就是如何讓CPU使用率曲線成爲一條正弦曲線,本文在Linux下實現這個效果。

程序運行時間

一個進程的運行時間大致分爲user time,kernel time和waiting time

三個時間加起來就是進程從開始到結束用的時間。
user time是進程在用戶空間執行的時間
kernel time是進程在內核空間執行的時間
waiting time是進程等待IO或者其他事件所用的時間
例如
  1. int main() 
  2.     int i; 
  3.     for(i = 0; i < 100000000; i ++) //用戶空間執行 
  4.         getpid(); //系統調用,內核空間執行 
  5.     //scanf和printf是C標準庫裏的,還要調用Linux的系統調用read和write 
  6.     scanf("%d\n",&i);  
  7.     printf("%d\n",i);  
  8.     return 0; 
使用time命令可以顯示進程運行的時間,real,user,sys。大部分時間都用來等IO了,因爲人輸入的速度遠小於計算機的速度。

CPU的使用率是指所有進程的user time和kernel time之和除以real time。而一般情況下,user time遠大於kernel time。
如果一個系統中CPU使用率幾乎爲0,那麼我們可以寫個程序控制CPU的使用率。
 
  1. while(1) 
  2.      for(i = 0; i < 100000; i ++);  //CPU忙,佔用的時間是user time 
  3.      usleep(10000); //CPU閒,屬於waiting time 
 
讓CPU一直維持在50%
 
讓進程50%的時間做循環,%50的時間sleep就行了
 
  1. int main() 
  2. int i; 
  3. while(1) 
  4. for(i = 0; i < n; i++); 
  5. usleep(m); 
  6. return 0; 
關鍵是如何確定n和m的值。
我們先把m定死,進程睡眠60ms,linux調度時的時間片好像是15ms,但是usleep精度較低,所以我們讓它睡的時間長點。
然後再確定n,先隨便給n一個值,看看彙編代碼裏使用了幾條指令:
 
  1. .L2: 
  2.         movl    $0, -8(%ebp) 
  3.         jmp     .L3 
  4. .L4: 
  5.         addl    $1, -8(%ebp) 
  6. .L3: 
  7.         cmpl    $3999999, -8(%ebp) 
  8.         jle     .L4 
  9.         movl    $60000, (%esp) 
  10.         call    usleep 
  11.         jmp     .L2 
紅色的是內層循環,有3條指令。CPU主頻是2.27GHz,如果按一個時鐘週期執行一條的話,那麼執行三條指令的時間爲2.27x10-9x3 = 6.9x10-9s,要執行60ms,循環數n的大小爲
60x10-3 / 6.9x10-9 = 8.69 x 106 
試一試這個數,8690000
CPU使用率穩定在38%左右,看來我們低估了CPU的能力。我們可以根據38%這個數計算出n的大小了
CPU時間/(CPU時間+60ms)= 38%,因此86900使用的cpu時間爲 36ms,8690000:36=n:60,求得n等於1448333,這次CPU使用率很精確的停留在50%左右。

 


我剛纔只注意進程cpu使用的CPU資源了,忘了還有其它進程的,其他進程目前大約佔了%3,稍微調整一下n,n=13200000,得到了50%的曲線,當然會有點波動。

 

注:上面說一個時鐘週期執行一條指令是不合適的,各種指令的執行時間不同。計算機裏的週期主要有時鐘週期,機器週期,指令週期。一條指令的週期稱爲指令週期,由幾個機器週期做成,而一個機器組成由幾個時鐘週期組成。上面的三條指令都需要取內存,因此時間長。如果把循環變量放在寄存器裏,那麼用的時間要小的多:

將內存數-8(%ebp)改爲寄存器數%ebx

  1. .L2: 
  2.         movl    $0, %ebx 
  3.         jmp     .L3 
  4. .L4: 
  5.         addl    $1, %ebx 
  6. .L3: 
  7.         cmpl    $13199999, %ebx 
  8.         jle     .L4  
  9.         movl    $60000, (%esp)  
  10.         call    usleep  
  11.         jmp     .L2 

CPU利用率由50%降到了20%。性能提高了60%。可見即使C語言寫的程序,也有很大的優化空間,其實編譯時加上-O也能達到這個效果。使用gcc的-O選項,也是20%,查看彙編代碼,發現就是把-8(%ebp)用%eax代替了,和我直接修改的是一樣。-O默認使用2級優化。其實gcc還可以進一步優化,將循環展開,不過這裏的n太大,展開的話代碼太多,優化到使用寄存器替代內存數就挺好了。
Java寫的程序,性能是C程序的幾十分之一!
 
讓CPU使用率爲正弦曲線
上面的程序n值在不同的機器上是不一樣的。我們換個思想實現。還是把睡眠時間定死,60ms。
  1. //僞代碼 
  2. int main() 
  3.     int start_time, current_time; 
  4.     while(1) 
  5.     { 
  6.         start_time = GetCurrentTime(); 
  7.         current_time = start_time; 
  8.         while(current_time - start_time < 60) 
  9.             current_time = GetCurrentTime(); 
  10.         sleep(60); 
  11.     } 
關鍵是如何獲取當前時間。Windows下可以使用GetTickCount(),Linux下可以使用gettimeofday()。
 
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. int main() 
  4.     struct timeval tv; 
  5.     long long start_time,end_time; 
  6.     while(1) 
  7.     { 
  8.         gettimeofday(&tv,NULL); 
  9.         start_time = tv.tv_sec*1000000 + tv.tv_usec; 
  10.         end_time = start_time; 
  11.      
  12.         while((end_time - start_time) < 60000) 
  13.         { 
  14.             gettimeofday(&tv,NULL); 
  15.             end_time = tv.tv_sec*1000000 + tv.tv_usec; 
  16.         } 
  17.         usleep(60000); 
  18.     } 
  19.     return 0; 

 

 

現在我們用這種方法實現CPU使用率的正弦曲線。

首先要確定這個曲線的函數。這個函數的最大值是1,最小值是0,因此肯定是0.5(sin(tx) + 1)。

怎麼確定t呢?

我們可以認爲,曲線的更新週期應該大於100ms,我們以100ms爲單位,把100ms的平均使用率作爲這100ms末的使用率。

 

假如我們希望10s能出一個完整的波形,100ms計算一次,那就需要計算100次。這樣我們要準備兩個大小爲100的數組,分別保存循環時間和睡眠時間。
而且滿足,第一個數組循環時間爲0,睡眠時間爲100,第50個數組循環時間爲100,睡眠時間爲0,第100個數組循環時間爲0,睡眠時間爲100.
這樣我們就確定了t了。100個數組下標爲橫座標,那麼週期是100,t=2x3.14/100 = 0.0628.
代碼如下:
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <math.h> 
  4. int main() 
  5.     struct timeval tv; 
  6.     long long start_time,end_time; 
  7.     long long busy_time[100]; 
  8.     long long idle_time[100]; 
  9.     int i; 
  10.     for(i = 0; i < 100; i++) 
  11.     { 
  12.         busy_time[i] = 100000 * 0.5 * (sin(i*0.0628) + 1); 
  13.         idle_time[i] = 100000 - busy_time[i]; 
  14.     } 
  15.     i = 0; 
  16.     while(1) 
  17.     { 
  18.         gettimeofday(&tv,NULL); 
  19.         start_time = tv.tv_sec*1000000 + tv.tv_usec; 
  20.         end_time = start_time; 
  21.      
  22.         while((end_time - start_time) < busy_time[i]) 
  23.         { 
  24.             gettimeofday(&tv,NULL); 
  25.             end_time = tv.tv_sec*1000000 + tv.tv_usec; 
  26.         } 
  27.         usleep(idle_time[i]); 
  28.         i = (i+1)%100;   
  29.     } 
  30.     return 0; 

 

效果還不錯:

 
上面的程序都是在單CPU下完成的。如果在多CPU下,可以指定此進程只運行在某個CPU上,不再贅述了。
 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章