把 CPU 「玩」起來

前言

從開始學習編程以後,就漸漸癡迷於技術,平時遇到購書滿減活動時就忍不住買一堆書。前兩天閒着無聊,翻開了去年買的《編程之美》,目錄裏的「讓 CPU 佔用率聽你指揮」吸引力個人眼球。這一年來搗鼓數據挖掘和機器學習,總會關注代碼運行效率,偶爾會思考如何提升 CPU、GPU 的利用率。因而立刻翻開了這一節。python

讓 CPU 利用率聽你指揮

翻開後是一道編程題(3星,須要查閱一些資料,在60分鐘內完成)編程

寫一個程序,讓用戶來決定 Windows 任務管理器(Task Manager)的 CPU 佔用率。程序設計的越精簡越好,語言不限。例如,可實現下面三種狀況:多線程

  1. CPU 和佔用率固定在50%,爲一條直線
  2. CPU 的佔用率爲一條直線,具體佔用率由命令行參數決定(參數範圍 1~100)
  3. 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 的多線程等知識又多了一點了解。感受技術仍是須要沉下心來才能學得好。

相關文章
相關標籤/搜索