前言
從開始學(xué)習(xí)編程之后,就漸漸癡迷于技術(shù),平時(shí)遇到購(gòu)書滿減活動(dòng)時(shí)就忍不住買一堆書。前兩天閑著無(wú)聊,翻開了去年買的《編程之美》,目錄里的“讓 CPU
占用率聽你指揮”吸引力我的眼球。這一年來(lái)?yè)v鼓數(shù)據(jù)挖掘和機(jī)器學(xué)習(xí),總會(huì)關(guān)注代碼運(yùn)行效率,偶爾會(huì)思考如何提高 CPU、GPU 的利用率。于是馬上翻開了這一節(jié)。
讓 CPU 利用率聽你指揮
翻開后是一道編程題(3星,需要查閱一些資料,在60分鐘內(nèi)完成)
寫一個(gè)程序,讓用戶來(lái)決定 Windows 任務(wù)管理器(Task Manager)的 CPU 占用率。程序設(shè)計(jì)的越精簡(jiǎn)越好,語(yǔ)言不限。例如,可實(shí)現(xiàn)下面三種情況:
* CPU 和占用率固定在50%,為一條直線
* CPU 的占用率為一條直線,具體占用率由命令行參數(shù)決定(參數(shù)范圍 1~100)
* CPU 的占用率狀態(tài)是一條正弦曲線
怎么實(shí)現(xiàn)呢
稍微想了想,如果想讓 CPU 跑滿,寫一個(gè)死循環(huán)就好了,讓 CPU 一直處于運(yùn)行狀態(tài),那 50%
的利用率要怎么實(shí)現(xiàn)呢?一半時(shí)間運(yùn)行一半時(shí)間休息,emmmmm。。休息。。突然想到了多線程里常用到的 sleep。接著往下看,確實(shí)是使用 sleep。
那就寫寫代碼吧
while True: for i in range(7200000): pass time.sleep(0.01)
這里稍微解釋下為什么是 7200000,以及為什么睡眠 0.01s(10ms)。
筆記本的 CPU 是 1.8Ghz,每秒運(yùn)行次數(shù)大概為 1.8 * 10^9 次,假設(shè) CPU 每個(gè)時(shí)鐘周期可以執(zhí)行兩條代碼,然后對(duì)于一段 for
循環(huán)代碼,轉(zhuǎn)換成匯編如下
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時(shí)重復(fù)循環(huán)
即5條代碼,所以,1S 內(nèi)循環(huán)次數(shù)為 1.8 * 10^9 * 2 / 5 = 720000000。而睡眠 10ms 是因?yàn)榻咏?Windows
的調(diào)度時(shí)間片。
運(yùn)行了一下,只是穩(wěn)定在 30% 左右,暫時(shí)先不調(diào)整循環(huán)次數(shù),接著往后看。
可以看出來(lái),這樣設(shè)置利用率很麻煩,那有沒有什么方法可以快點(diǎn)設(shè)置呢?
重新看看上面這段代碼, 7200000 次循環(huán)花費(fèi)的時(shí)間大約為 10ms,那意思就是 CPU 運(yùn)行 10ms 然后再休息 10ms,再運(yùn)行 10ms 再休息
10ms,接著運(yùn)行 10ms 然后再休息 10ms ······ 想必肯定看出來(lái)什么了吧,我們只需要設(shè)置 CPU 運(yùn)行多少時(shí)間就好了!于是可以寫出下面代碼
busyTime = 0.01 while True: startTime = time.clock() while((time.clock() -
startTime) <= busyTime): pass time.sleep(busyTime)
運(yùn)行一下,跟剛剛差不太多,穩(wěn)定在 30% 左右
正弦函數(shù)
這時(shí)候,我們也可以很容易就寫出跑成正弦函數(shù)圖像的代碼了,不斷改變運(yùn)行與空閑的時(shí)間比就好了。
import time import mathimport affinity from multiprocessing import Process,
cpu_countdef exec_fun(): SAMPLING_COUNT = 200 # 抽樣點(diǎn)數(shù)量 PI = math.pi # pi
TOTAL_AMPLITUDE = 300# 每個(gè)抽樣點(diǎn)對(duì)應(yīng)時(shí)間片 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()
運(yùn)行一下。emmmmmmmmmmmm。。。。等一下,不對(duì)啊,怎么不是正弦函數(shù)形狀呢?
?
?這跟說(shuō)好的好像不太一樣啊。是不是因?yàn)橛玫氖?python,跑的本來(lái)就慢的原因?那試試 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; }
?
再運(yùn)行一下,它怎么還是這樣???
于是乎搗鼓了 2 個(gè)小時(shí)。。。
……
……
……
后來(lái)仔細(xì)想了想,CPU 是 4 核 8 處理器的,不會(huì)是任務(wù)分?jǐn)偟搅藥讉€(gè)處理器上了吧?于是查了查如何把當(dāng)前進(jìn)程放在一個(gè)處理器上執(zhí)行。
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)
運(yùn)行一下,好的,它成了!??!
?
?順便解決下上面C++的代碼,在 main() 函數(shù)最開始加入下面代碼
SetThreadAffinityMask(GetCurrentThread(), 1);
小節(jié)
好久沒有這樣子搗鼓過(guò)東西了,想想上次做操作系統(tǒng)課設(shè)的時(shí)候,要獲取系統(tǒng)的信息,當(dāng)時(shí)只是為了完成任務(wù)就沒有去深究一些東西,這次搗鼓了 CPU
的利用率控制之后,對(duì)進(jìn)程、CPU 以及 python 的多線程等知識(shí)又多了一點(diǎn)了解。感覺技術(shù)還是需要沉下心來(lái)才能學(xué)得好。
熱門工具 換一換
