title: CPU占用率曲线
date: 2018-11-29 15:15:06
tags: 算法
categories: 十一月,2018
题目:
写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率。程序越精简越好,计算机语言不限。
1.CPU的占用率固定在50%,为一条直线;
2.CPU的占用率为一条直线,具体占用率由命令行参数决定(参数范围1~100);
3.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<n;i++);又该如何来估算这个最合适的n值呢?我们都知道CPU执行的是机器指令,而最接近于机器指令的语言是汇编语言,所以我们可以先把这个空循环简单的写成如下汇编代码(此代码为示意性的伪代码)后再进行分析:
next:
mov eax, dword ptr [i] ; i放入寄存器
add eax, 1 ; 寄存器加1
mov dword ptr [i] ,eax ; 寄存器赋回1
cmp eax,sword ptr [n] ; 比较i和n
jl next ; i小于n时重复循环
假设这段代码要运行的cpu是P4 2.4Ghz(2.4*10的9次方个时钟周期每秒)。现在CPU每个时钟周期可以执行两条以上的代码,我们取平均值两条,于是有(2 400 000 000 * 2)/5 = 960 000 000(循环/秒),也就是说CPU1秒钟可以运行这个空循环960 000 000次。不过我们还是不能简单地将 n=960 000 000,然后Sleep(1000)了事。如果我们让CPU工作1秒钟,然后休息1秒钟,波形很有可能就是锯齿状的一个先达到一个峰值(>50%),然后跌到一个很低的占用率。
我们尝试着降低两个数量级,令n = 9 600 000,而睡眠时间则相应地改为10毫秒(Sleep(10))。用10毫秒是因为比较接近Windows的调度时间片。如果选得太小(比如1毫秒),会造成线程频繁地被唤醒和挂起,无形中又增加了内核时间的不确定性。最后我们可以得到代码
int main(){
for(; ;){
for(int i = 0; i<9600000; i++){
Sleep(10);
}
}
return 0;
}
在不断调整9 600 000的参数后,我们就可以在一台指定的机器上获得一条大致稳定的50%CPU占用率直线。
使用这种方法要注意两点影响:
1.尽量减少sleep/awake的频率,以减少操作系统内核调度程序的干扰;
2.尽量不要调用system call (比如I/O这些privilege instruction),因为它也会导致很多不可控的内核运行时间。
这个方法缺点也很明显:不能适应机器差异性。一旦换了一个CPU,我们又得重新估算n值。
解法二
使用GetTickCount()和Sleep()
我们知道GetTickCount()可以得到“系统启动到现在”所经历时间的毫秒值,最多能够统
计到49.7天。我们可以利用GetTickCount()来判断busy loop要循环多久,伪代码如下。
const DWORD busyTime = 10; //10 ms
const DWORD int idleTime = busyTime; //相同的比率将导致50%的CPU使用率。
Int64 startTime = 0;
while(true){
DWORD startTime = GetTickCount();
//busy loop
while((GetTickCount() - startTime) <= busyTime)
;
//idle loop
Sleep(idleTime);
}
这两种解法都是假设目前系统上只有当前程序在进行,但实际上,操作系统中有很多程序会同时执行各种各样的任务,如果此时其他进程使用了10%的CPU,那我们的程序就只能使用40%的CPU,这样才能达到50%的效果。
解法三
能动态适应的解法
//c# code
static void MakeUsage(float level){
PerformanceCounter p = new PerformanceCounter("Processor","%Processor Time","_Total");
while(true){
if(p.NextValue() > lecel)
System.Threading.Thread.Sleep(10);
}
}
解法四
正弦曲线
//C++ code to make task manager generate sine graph
#include "windows.h"
#include "stdlib.h"
#include "math.h"
//把一条正弦曲线0~2n之间的弧度等分成200份进行抽样,计算每个抽样点的振幅
//然后每隔300ms的时间取下一个抽样点,并让CPU工作对应振幅的时间
const int SAMPLING_COUNT = 200; //抽样点数量
const double PI = 3.1415926535; //pi值
const int TOTAL_AMPLITUDE = 300; //每个抽样点对应的时间片
int _tmain()(int argc, _TCHAR* argv[]){
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(PI * radian) * amplitude));
radian += radianIncrement;
}
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;
}
总结
1.Sleep() --这个方法能让当前线程"停"下来。
2.WaitForSingleObject() --自己停下来,等待某个事件发生。
3.GetTickCount() --有人把Tick翻译成"嘀嗒",很形象
4.QueryPerformanceFrequency()、QueryPerformanceCounter() --让你访问到精度更高的CPU数据。
5.timeGetSystemTime() --另一个得到高精度时间的方法。
6.PerformanceCounter --效能计数器
7.GetProcessorInfo()/SetThreadAffinityMask()。遇到多核的问题怎么办呢?这两个方法能够帮你更好的控制CPU。
8.GetCPUTickCount().–得到CPU核心运行周期数