首页 > 代码库 > 编程之美读书笔记1.1——让CPU占用率曲线听你的指挥

编程之美读书笔记1.1——让CPU占用率曲线听你的指挥

http://blog.csdn.net/pipisorry/article/details/36189155


<strong><span style="font-size:48px;color:#ff0000;">问题:</span></strong>写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率。

假设机器是多CPU,上面的程序会出现什么结果?怎样在多个CPU时显示相同的状态?比如。在双核的机器上,假设让一个单线程的程序死循环,能让两个CPU的使用率达到50%的水平么?为什么?多CPU的问题首先须要获得系统的CPU信息。能够使用GetProcessorInfo()获得多处理器的信息,然后指定进程在哪一个处理器上执行。

当中指定执行使用的是SetThreadAffinityMask()函数。

书中提到两个重要的API,GetProcessorInfo()与SetThreadAffinityMask()。经查询MSDN。GetProcessorInfo()是Windows Server上的API,在PC机上不好用。所以能够尝试GetSystemInfo()来获得核数。

实际上在Windows平台上直接用任务管理器就能够查看。任务管理器上会用四个窗体来显示CPU的使用情况(Windows7 酷睿i5-3230M)


四核都显示正弦曲线算法:

參考代码:

/*********************************************************************************************/
/* 控制cpu占用率曲线(50%直线、1~100随意直线、正弦曲线)皮皮 2014-6-30 */
/********************************************************************************************/
#include <Windows.h>
#include <stdio.h>
#include <math.h>
using namespace std;

const int SAMPLE_NUM = 200;
DWORD busy_time[200];
const DWORD TOTAL_TIME_SLICE = 100; //每隔100ms取下一个採样点。让cpu工作在相应振幅的时间
const DWORD HALF_TOTAL_TIME_SLICE = TOTAL_TIME_SLICE/2;
//sine 曲线
void sine(){
DWORD pre_time;
for(int i = 0 ; ; i = (i+1)%SAMPLE_NUM ){
pre_time = GetTickCount();
while( (GetTickCount() - pre_time) <= busy_time[i]);//cpu忙
Sleep(TOTAL_TIME_SLICE - busy_time[i]);//cpu闲
}
}


const double PI_L = 0.031415926535;
//对sine採样
void sine_sampling(){
for(int i = 0 ; i < SAMPLE_NUM ; i++){
busy_time[i] = (DWORD)( sin( PI_L*i )*HALF_TOTAL_TIME_SLICE + HALF_TOTAL_TIME_SLICE );//busy_time值范围 (-1~1) *50+50 = (0~100)
//printf("busy_time[%d] = %d\n",i,busy_time[i]);
}
}


void main(){
sine_sampling();

//printf("CurrentProcessorNumber : %d\n",(int)GetCurrentProcessorNumber());
SYSTEM_INFO info;
    GetSystemInfo(&info); //调用API函数来获取计算机硬件的信息
printf("dwNumberOfProcessors : %d\n",info.dwNumberOfProcessors);//cpu核数

DWORD dwThreadId;
HANDLE hThread;
int coreNum = info.dwNumberOfProcessors;
for(int i = 0 ; i < coreNum ; i ++){
hThread = CreateThread(0,0,(LPTHREAD_START_ROUTINE)sine,0,0,&dwThreadId);
SetThreadAffinityMask(hThread,1<<i);//针对cpu核i(from 0)
}
    WaitForSingleObject(hThread, INFINITE); //传入INFINITE表示无限等待
}


代码部分解释:

1.CreateThread

函数功能:创建线程

函数原型:

CreateThread(
    __in_opt  LPSECURITY_ATTRIBUTES lpThreadAttributes,
    __in      SIZE_T dwStackSize,
    __in      LPTHREAD_START_ROUTINE lpStartAddress,
    __in_opt __deref __drv_aliasesMem LPVOID lpParameter,
    __in      DWORD dwCreationFlags,
    __out_opt LPDWORD lpThreadId
    );

函数说明:

第一个參数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

第二个參数表示线程栈空间大小。

传入0表示使用默认大小(1MB)。

第三个參数表示新线程所运行的线程函数地址,多个线程能够使用同一个函数地址。

第四个參数是传给线程函数的參数。

第五个參数指定额外的标志来控制线程的创建,为0表示线程创建之后马上就能够进行调度。假设为CREATE_SUSPENDED则表示线程创建后暂停执行。这样它就无法调度,直到调用ResumeThread()

第六个參数将返回线程的ID号,传入NULL表示不须要返回该线程ID号。

函数返回值:成功返回新线程的句柄。失败返回NULL 


 2.WaitForSingleObject

函数功能:等待函数 – 使线程进入等待状态。直到指定的内核对象被触发。

函数原形:

WaitForSingleObject(
    __in HANDLE hHandle,
    __in DWORD dwMilliseconds
    );

函数说明:

第一个參数为要等待的内核对象。

第二个參数为最长等待的时间,以毫秒为单位,如传入5000就表示5秒。传入0就马上返回。传入INFINITE表示无限等待。

由于线程的句柄在线程执行时是未触发的。线程结束执行。句柄处于触发状态。所以能够用WaitForSingleObject()来等待一个线程结束执行。

函数返回值:在指定的时间内对象被触发。函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入參数有错误将返回WAIT_FAILED


3.SetThreadAffinityMask()

函数功能:Sets a processor affinity mask for the specified thread.

函数原形:

WaitForSingleObject(
    __in HANDLE hHandle,
    __in DWORD dwMilliseconds
    );

函数说明:

这个函数的參数有两个,第一个是运行任务的句柄,第二个是核的掩蔽码。

掩蔽码的使用规则是假设最低位置1,就将任务分派给编号为0的核,假设次低位置1,就将任务分派给编号为1的核。

假设掩蔽码设置为0xC。即编号为2, 3的核被置为1,线程就会在这两个核上切换。

more:

进程与指定cpu绑定:SetProcessAffinityMask(GetCurrentProcess(), dwMask);

线程与指定cpu绑定:SetThreadAffinityMask(GetCurrentThread(),dwMask);

dwMask为CPU序号的或运算值:1(0001)代表仅仅执行在CPU1,2(0010)代表仅仅执行在CPU2。3(0011)代表能够执行在CPU1和CPU2,以此类推。

ref: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686247(v=vs.85).aspx


结果截图:

技术分享




附:

1.TOTAL_TIME_SLICE 的计算

假设不考虑其他程序的CPU占用情况,能够在每一个核上开一个线程。执行指定的函数,实现每一个核的CPU占用率同样。 

要让CPU的占用率。呈函数 y = calc(t) (0 <= y <= 1, t为时间,单位为ms )分布。仅仅要取间隔非常短的一系列点。觉得在某个间隔内。y值近似不变。

设间隔值为GAP。显然在指定t值附近的GAP这段时间内,

CPU占用时间为:busy = GAP * calc(t)。

CPU空暇时间为:idle = GAP – busy

因此,非常easy写出以下这个通用函数: 

技术分享void solve(Func *calc)
技术分享
{
技术分享  
double tb = 0;
技术分享  
while(1{
技术分享    unsigned ta 
= get_time();
技术分享    
double r = calc(tb);
技术分享    
if (r < 0 || r > 1) r = 1;
技术分享    DWORD busy 
= r * GAP;
技术分享    
while(get_time() - ta < busy) {}
技术分享    Sleep(GAP 
- busy);
技术分享    tb 
+= GAP;
技术分享  }

技术分享}

假设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.也就是说,仅仅要确定了周期和计算次数,其他几个參数也都确定下来。


2.CPU亲缘性(Affinity)介绍

依照默认设置。当系统将线程分配给处理器时,Windows使用软亲缘性来进行操作。这意味着假设全部其它因素同样的话,它将设法在它上次执行的那个处理器上执行线程。让线程留在单个处理器上。有助于反复使用仍然在处理器的内存快速缓存中的数据。

有一种新的计算机结构,称为NUMA(非统一内存訪问),在该结构中,计算机包括若干块插件板,每一个插 件板上有4个CPU和它自己的内存区。
当CPU訪问的内存是它自己的插件板上的内存时,NUMA系统执行的性能最好。假设CPU须要訪问位于还有一个插件板上的内 存时,就会产生巨大的性能减少。在这种环境中,就须要限制来自一个进程中的线程在共享同一个插件版的CPU上执行。为了适应这种计算机结构的须要。Windows同意你设置进程和线程的亲缘性。

换句话说。你能够控制哪个CPU能够执行某些线程。

这称为硬亲缘性。

请注意。子进程能够继承进程的亲缘性。

注意:

(1)不管计算机中实际拥有多少个CPU。Windows98及曾经系统仅仅使用一个CPU,上述API不被支持。

(2)在大多数环境中,改变线程的亲缘性就会影响调度程序有效地在 各个CPU之间移植线程的能力,而这样的能力能够最有效地使用CPU时间。


3.CreateThread()函数

它是Windows提供的API接口,在C/C++语言另有一个创建线程的函数_beginthreadex(),在非常多书上(包含《Windows核心编程》)提到过尽量使用_beginthreadex()来取代使用CreateThread()。这是为什么?http://blog.csdn.net/morewindows/article/details/7421759


ref:

http://blog.csdn.net/pipisorry/article/details/36189155

http://blog.csdn.net/morewindows/article/details/7421759

http://www.cnblogs.com/foreverinside/p/3577273.html

http://www.cnblogs.com/flyinghearts/archive/2011/03/22/1991965.html

http://blog.csdn.net/u012370255/article/details/28232153

编程之美读书笔记1.1——让CPU占用率曲线听你的指挥