教你玩转CPU——控制CPU占用率曲线

  • 格式:doc
  • 大小:44.00 KB
  • 文档页数:5

下载文档原格式

  / 11
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

教你玩转CPU----随心所欲控制CPU占用率曲线走向

我想大家都知道电脑的任务管理器里面有个CPU占用率曲线吧,他根据系统使用资源的不同实时显示,那能不能使这个曲线按照自己的思想显示呢,如使他一直保持一个固定的值,如50%,或者正弦曲线,听起来好像很难,但如果是让你用C、C++或java等语言写一个最简短的程序,你会不会觉得更难呢,不错,这曾经是微软亚洲研究院(Microsoft Research Asia,MSRA)的一道面试题,据说这道题就淘汰了当批接受面试的85%的人,他的原题如下:

1、CPU的占用率固定为50%,为一条直线;

2、CPU的占用率为一条直线,但是具体占用率由命令行参数决定(参数范围1~100);

3、CPU的占用率状态是一个正弦曲线。

看来这并不是不可能完成的任务。然我们仔细回想一下写程序时曾经碰到的问题,如果我们不小心写了一个死循环,CPU占用率就会跳到最高,并且一直保持100%。我们也可以打开任务管理器,实际观测一下它是怎样变动的。凭肉眼观察,它大约是每一秒钟更新一次。一般情况下,CPU的占用率很低。但是当用户运行一个程序时,执行一些复杂的操作时,CPU 的使用率会急剧增加。当用户鼠标晃动时,CPU的使用率也会有小幅度的变化。

那么当任务管理器报告CPU的使用率为0的时候,谁在使用CPU呢?通过任务管理器的“进程(Process)”一栏可以看到,System Idle Process占用了CPU空闲的时间----这时候大家该回忆起在“操作系统原理”这门课上学到的一些知识了吧。系统中有那么多的进程,他们什么时候能“闲下来”呢?答案很简单,这些程序在等待用户的输入,或者在等待某些事件的发生,或者主动进入休眠状态。

在任务管理器的一个刷新周期内,CPU忙(执行应用程序)的时间和刷新周期总时间的比率,就是CPU的占用率,也就是说,任务管理器显示的是每个刷新周期内CPU占用率的统计平均值。因此,我们写了一个程序,让他在任务管理器的刷新期内一会儿忙,一会儿闲,然后通过调节忙\闲比例,就可以控制任务管理器中显示的CPU占用率。

要操纵CPU的使用率曲线,就需要使CPU在一段时间内(更具Task Manager的采样率)跑busy和idle两个不同的循环(loop),从而通过不同的时间比例,来调节CPU使用率。

busy loop可以通过执行空循环来实现,idle可以通过Sleep()实现。

问题的关键在于如何控制两个loop时间,我们先实验一下Sleep一段时间,然后循环n 次,估算n的值。

那么对于一个空循环 for(i=0;i

loop:

mov dx i ;将i置入dx寄存器

inc dx ;将dx寄存器加1

mov i dx ;将dx中的值赋回i

cmp i n ;比较i和n

j1 loop ;1小于n时则重复循环

假设这段代码要运行的CPU是P4 2.4GHz(2.4*10的9次方个时钟周期每秒)。现代CPU每个时钟周期可以执行两条以上的代码,呢么我们就取平均值两条,于是让(2400 000 000*2)/5=960 000 000(循环/秒),也就是说CPU1秒钟可以运行这个空循环960 000 000次。不过我们还是不能简单的将n=960 000 000,然后Sleep(1000)了事。如果我们让CPU 工作1秒钟,然后休息1秒钟,波形很可能就是锯齿状的----先达到一个峰值(>50%),然后跌倒一个很低的占用率。

我们尝试降低两个数量级,令n=960 0000而睡眠时间相应改为10毫秒(Sleep(10))。用10毫秒是因为它不大也不小,比较接近Windows的调度时间片。如果选得太小(比如1毫秒),则会造成线程频繁的被唤醒和挂起,无形中友增加了内核时间的不确定性影响。最后我们得到如下代码:

int main()

{

for(; ; )

{

for(int i = 0; i < 9600000; i++)

;

Sleep(10);

}

return 0;

}

在VC中用Sleep前面要加上#include "windows.h",在不断调整9600000的参数后,我们就可以在一台指定的机器上获得一条大致稳定50%CPU占用率曲线

使用这种方法要注意两点影响:

1、尽量减少sleep/awake的频率,减少操作系统内核调度程序的干扰。

2、尽量不要调用system call (比如I/O这些privilege instruction),因为它也会导致很多不可控的内核运行时间。

3、上面的程序是针对单核CPU的,如果是多核CPU,那么CPU占用率曲线有好几个,如果自定义指定哪个或哪几个显示呢,

可以在 for(; ; )前面加上SetProcessAffinityMask()函数,

BOOL SetProcessAffinityMask(HANDLE hProcess, DWORD_PTR dwProcessAffinityMask);

第一个参数用来指定指定哪个进程,传入它的句柄。第二个进程用来指定哪个CPU核心来执行此进程。

DWORD_PTR,其实就是unsigned long*.Unsigned long type for pointer e when casting a pointer to a long type to perform pointer arithmetic.(Also commonly used for general 32-bit parameters that have been extended to 64 bits in 64-bit windows.)

DWORD 其实就是unsigned long。Windows下常用来保存地址或存放指针。

比如这样调用函数:

::SetProcessAffinityMask(::GetCurrentProcess(),0x1);可以指定当前执行的进程在第一个CPU上运行(00000001)。对于双核CPU,

::SetProcessAffinityMask(::GetCurrentProcess(),0x2);可以指定在第二个CPU上运行。(00000010)

::SetProcessAffinityMask(::GetCurrentProcess(),0x3);可以允许在两个CPU上任意运行。(000000011)

::SetProcessAffinityMask(::GetCurrentProcess(),0x3);可以允许在第三个CPU上任意运行。(000000100)

::SetProcessAffinityMask(::GetCurrentProcess(),0x3);可以允许在第一个和第三个CPU上任意运行。(00000101)

以此类推。。。

该方法的缺点很明显:不能适应机器的差异性。一旦换了一个CPU,我们又得重新估算n值。有没有方法动态的了解CPU的运算能力,然后自动调节忙/闲的时间比呢?

当然可以,我们知道GetTickCount()可以得到“系统启动到现在”所经历的时间的毫秒值。我们可以利用GetTickCount()来判断busy要循环多久,核心代码如下:

int busyTime = 10; // 10 ms

int idleTime = busyTime; // same ratio will lead to 50% cpu usage Int64 startTime = 0;

while(true)

{

startTime = GetTickCount();

// busy loop

while((GetTickCount() - startTime) <= busyTime)

;

// idle loop

Sleep(idleTime);

}