CPU占用率曲线


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核心运行周期数

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值