首页 > 代码库 > 编程之美读书笔记1.1——让CPU占用率曲线听你的指挥
编程之美读书笔记1.1——让CPU占用率曲线听你的指挥
http://blog.csdn.net/pipisorry/article/details/36189155
问题:如果机器是多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
因此,很容易写出下面这个通用函数:
{
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