從開始學習編程以後,就漸漸癡迷於技術,平時遇到購書滿減活動時就忍不住買一堆書。前兩天閒着無聊,翻開了去年買的《編程之美》,目錄裏的「讓 CPU 佔用率聽你指揮」吸引力個人眼球。這一年來搗鼓數據挖掘和機器學習,總會關注代碼運行效率,偶爾會思考如何提升 CPU、GPU 的利用率。因而立刻翻開了這一節。python
翻開後是一道編程題(3星,須要查閱一些資料,在60分鐘內完成)編程
寫一個程序,讓用戶來決定 Windows 任務管理器(Task Manager)的 CPU 佔用率。程序設計的越精簡越好,語言不限。例如,可實現下面三種狀況:多線程
- CPU 和佔用率固定在50%,爲一條直線
- CPU 的佔用率爲一條直線,具體佔用率由命令行參數決定(參數範圍 1~100)
- CPU 的佔用率狀態是一條正弦曲線
怎麼實現呢app
稍微想了想,若是想讓 CPU 跑滿,寫一個死循環就行了,讓 CPU 一直處於運行狀態,那 50% 的利用率要怎麼實現呢?一半時間運行一半時間休息,emmmmm。。休息。。忽然想到了多線程裏經常使用到的 sleep。接着往下看,確實是使用 sleep。機器學習
那就寫寫代碼吧函數
while True: for i in range(7200000): pass time.sleep(0.01)
這裏稍微解釋下爲何是 7200000,以及爲何睡眠 0.01s(10ms)。學習
筆記本的 CPU 是 1.8Ghz,每秒運行次數大概爲 1.8 * 10^9 次,假設 CPU 每一個時鐘週期能夠執行兩條代碼,而後對於一段 for 循環代碼,轉換成彙編以下spa
next: mov eax, dword ptr[i] ; i放入寄存器 add eax, 1 ; 寄存器+1 mov dword ptr [i], eax ; 寄存器賦回i cmp eax, dword ptr [i] ; 比較i和n j1 next ; i小於n時重複循環
即5條代碼,因此,1S 內循環次數爲 1.8 * 10^9 * 2 / 5 = 720000000。而睡眠 10ms 是由於接近 Windows 的調度時間片。操作系統
運行了一下,只是穩定在 30% 左右,暫時先不調整循環次數,接着日後看。命令行
能夠看出來,這樣設置利用率很麻煩,那有沒有什麼方法能夠快點設置呢?
從新看看上面這段代碼, 7200000 次循環花費的時間大約爲 10ms,那意思就是 CPU 運行 10ms 而後再休息 10ms,再運行 10ms 再休息 10ms,接着運行 10ms 而後再休息 10ms ······ 想必確定看出來什麼了吧,咱們只須要設置 CPU 運行多少時間就行了!因而能夠寫出下面代碼
busyTime = 0.01 while True: startTime = time.clock() while((time.clock() - startTime) <= busyTime): pass time.sleep(busyTime)
運行一下,跟剛剛差不太多,穩定在 30% 左右
正弦函數
這時候,咱們也能夠很容易就寫出跑成正弦函數圖像的代碼了,不斷改變運行與空閒的時間比就行了。
import time import mathimport affinity from multiprocessing import Process, cpu_count def exec_fun(): SAMPLING_COUNT = 200 # 抽樣點數量 PI = math.pi # pi TOTAL_AMPLITUDE = 300 # 每一個抽樣點對應時間片 busySpan = [] amplitude = TOTAL_AMPLITUDE / 2 radianIncrement = 2.0 / SAMPLING_COUNT radian = 0.0 for i in range(SAMPLING_COUNT): busySpan.append((amplitude + math.sin(PI * radian) * amplitude) / 1000.0) radian += radianIncrement # print(busySpan[i], TOTAL_AMPLITUDE - busySpan[i]) j = 0 while True: startTime = time.clock() # print(startTime) while ((time.clock() - startTime) <= busySpan[j]): pass # print('sleep') time.sleep(0.3 - busySpan[j]) j = (j + 1) % SAMPLING_COUNT exec_fun()
運行一下。emmmmmmmmmmmm。。。。等一下,不對啊,怎麼不是正弦函數形狀呢?
這跟說好的好像不太同樣啊。是否是由於用的是 python,跑的原本就慢的緣由?那試試 C++ 吧
#include<stdlib.h> #include<Windows.h> #include<math.h> const int SAMPLING_COUNT = 150; const double PI = 3.1415926535; const int TOTAL_AMPLITUDE = 300; int main() { DWORD busySpan[SAMPLING_COUNT]; int amplitude = TOTAL_AMPLITUDE / 2; double radian = 0.0; double radianIncrement = 2.0 / (double)SAMPLING_COUNT; for (int i = 0; i < SAMPLING_COUNT; i++) { busySpan[i] = (DWORD)(amplitude + sin(radian * PI) * amplitude); radian += radianIncrement; printf("%d\t%d\n", busySpan[i], TOTAL_AMPLITUDE - busySpan[i]); } DWORD startTime = 0; for (int j = 0;; j = (j + 1) % SAMPLING_COUNT) { startTime = GetTickCount(); while ((GetTickCount() - startTime) <= busySpan[j]); Sleep(TOTAL_AMPLITUDE - busySpan[j]); } return 0; }
再運行一下,它怎麼仍是這樣???
因而乎搗鼓了 2 個小時。。。
……
……
……
後來仔細想了想,CPU 是 4 核 8 處理器的,不會是任務分攤到了幾個處理器上了吧?因而查了查如何把當前進程放在一個處理器上執行。
if __name__ == "__main__": p = Process(target=exec_fun) p.start() pid = p.pid print(affinity.get_process_affinity_mask(pid)) affinity.set_process_affinity_mask(pid, 1)
運行一下,好的,它成了!!!
順便解決下上面C++的代碼,在 main() 函數最開始加入下面代碼
SetThreadAffinityMask(GetCurrentThread(), 1);
很久沒有這樣子搗鼓過東西了,想一想上次作操做系統課設的時候,要獲取系統的信息,當時只是爲了完成任務就沒有去深究一些東西,此次搗鼓了 CPU 的利用率控制以後,對進程、CPU 以及 python 的多線程等知識又多了一點了解。感受技術仍是須要沉下心來才能學得好。