《编程之美》学习笔记——指挥CPU占用率

问题:

写一个程序。让用户来决定Windows任务管理器(Task Manager)的CPU占用率(单核)。

有下面几种情况:
1.CPU占用率固定在50%,为一条直线
2.CPU的占用率为一条直线,详细占用率由命令行參数决定(范围1~100)
3.CPU的占用率状态为一条正弦曲线
4.多核处理器情况下上述问题怎么解决


分析与解答

首先确定CPU占用率的定义,即在任务管理器的一个刷新周期内。CPU忙(运行应用程序)的时间和刷新周期总时间的比率,就是CPU的占用率。也能够说成,任务管理器中显示的是每一个刷新周期内CPU占用率的统计平均值。
所以能够写个程序。在一个刷新周期中。一会儿忙,一会儿闲,调节忙/闲比例,就能够控制CPU占有率了。


一个刷新时间是多久,书上说,通过对任务管理器观測,大约是1秒。鼠标移动、后台程序等都会对曲线造成影响!


单核环境下。空死循环会导致100%的CPU占用率。双核环境下,CPU总占用率大约为50%。四核是25%左右。




解法一:简单解法 

Busy用可循环来实现,for(i=0;i<n;i++) ;
相应的汇编语言为
loop;
mov dx i     ;将i置入dx寄存器
inc dx       ;将dx寄存器加1
mov dx i     ;将dx中的值赋回i
cmp i n      ;比較i和n
j1 loop      。i小于n时则反复循环

我的cpu是 I5 2410M 2.30GHZ(双核四线程,如图)  由于眼下的cpu每一个时钟周期可运行两条以上的代码,取平均值2,于是(2300000000*2)/5=920000000(循环/秒) 每秒能够运行循环920000000次。

不能简单的取n=920000000然后sleep(1000)。假设让cpu工作1s,歇息1s非常可能是锯齿。先达到一个峰值然后跌入一个非常低的占有率。所以我们睡眠时间改为100ms。100ms比較接近windows的调度时间,n=92000000。假设sleep时间选的太小。会造成线程频繁的唤醒和挂起,无形中添加了内核时间的不确定性因此代码例如以下:

#include <windows.h>  

int main(void)  
{  
	//Run on CPU 0(0x00000001)(00000001)
	//Run on CPU 1(0x00000002)(00000010)
	//Run on CPU 0 AND CPU 1(0x00000003)(00000101)
	//Run on CPU 2(0x00000004)(00000100)
	//......
 	//SetProcessAffinityMask(GetCurrentProcess(),0x1);//进程与指定cpu绑定
 	SetThreadAffinityMask(GetCurrentThread(), 0x1);//线程与指定cpu绑定
 	while(true)  
	{  
 		for(int i=0;i<92000000;i++)  
 			;  
 		Sleep(100);  
 	}  
 	return 0;  
} 

使用SetProcessAffinityMask函数。进程与CPU绑定。得到例如以下图。

使用SetThreadAffinityMask函数,进程与CPU绑定,得到例如以下图。


解法二:使用GetTickCount()和Sleep()

GetTickCount()能够得到“系统启动到如今”所经历的时间的毫秒值,最多能够统计49.7天,能够利用GetTickCount()推断循环的时间,代码例如以下:

#include <windows.h>  

const int busyTime=100;  
const int idleTime=busyTime;

int main(void)  
{   
	double startTime;  
	SetProcessAffinityMask(GetCurrentProcess(), 0x1);  
	while(true)  
	{  
		startTime=GetTickCount();  
		while((GetTickCount() - startTime) <= busyTime)  
		{  
			;  
		}  
		Sleep(idleTime);  
	}  
	return 0;  
} 
效果例如以下图所看到的,与第一种 解法效果差点儿相同,由于都如果当前系统仅仅有当前程序在执行,但实际上,操作系统有非常多程序会同一时候调试执行各种任务,如果此刻进程使用20%的cpu。那我们的程序仅仅有使用30%的cpu才干达到50%的效果。

解法三:能动态适应的解法

使用 .Net Framework提供的PerformanceCounter进行查询。详细代码是用C#写的
using System;
using System.Diagnostics; 
namespace CPU
{
    class Program
    {
        static void Main(string[] args)
        {
           cpu(1000);  
        }  
        static void cpu(double level)  
        {  
            PerformanceCounter p = new PerformanceCounter("Processor", "% Processor Time", "_Total");  
            if (p == null)  
            {  
                return;  
            }  
            while (true)  
            {  
                if (p.NextValue() > level)  
                    System.Threading.Thread.Sleep(1000);  
            }  
        }  
    }
}

解法四:正弦曲线

#include <windows.h>  
#include <math.h>  
int main(void)  
{  
	SetProcessAffinityMask(GetCurrentProcess(), 0x1);  
	const double SPLIT=0.01;  
	const int COUNT=200;  
	const double PI=3.14159265;  
	const int INTERVAL=300;  
	DWORD busySpan[COUNT]; //array of busy time  
	DWORD idleSpan[COUNT]; //array of idle time  
	int half=INTERVAL/2;  
	double radian=0.0;  
	for(int i=0;i<COUNT;i++)  
	{  
		busySpan[i]=(DWORD)(half+(sin(PI*radian)*half));  
		idleSpan[i]=INTERVAL-busySpan[i];  
		radian+=SPLIT;  
	}  
	DWORD startTime=0;  
	int j=0;  
	while(true)  
	{  
		j=j%COUNT;  
		startTime=GetTickCount();  
		while((GetTickCount()-startTime)<=busySpan[j])  
			;  
		Sleep(idleSpan[j]);  
		j++;  
	}  
	return 0;  
}  

进一步讨论:

控制CPU占用率。由于要调用Windows的API。要考虑到多核、超线程的情况,要考虑到不同版本号的Windows的计时相关的API的精度不同,使问题变得相当复杂,若再考虑其他程序的CPU占用率,则该问题则变得非常烦人。(taskmgr调用了一个未公开的API)。对CPU核数的推断,书上是调用GetProcessorInfo。事实上能够直接调用GetSystemInfo,SYSTEM_INFO结构的dwNumberOfProcessors成员就是核数。

假设不考虑其他程序的CPU占用情况,能够在每一个核上开一个线程,执行指定的函数。实现每一个核的CPU占用率同样。假设CPU占用率曲线不是周期性变化,就要对每一个t值都要计算一次,否则,能够仅仅计算第一个周期内的各个t值。其他周期的直接取缓存计算结果。

#include<iostream>  
#include<cmath>  
#include<windows.h>  
  
static int PERIOD = 60 * 1000; //周期ms  
const int COUNT = 300;  //一个周期计算次数  
const double GAP_LINEAR = 100;  //线性函数时间间隔100ms  
const double PI = 3.1415926535898; //PI  
const double GAP = (double)PERIOD / COUNT; //周期函数时间间隔  
const double FACTOR = 2 * PI / PERIOD;  //周期函数的系数  
static double Ratio = 0.5;  //线性函数的值 0.5即50%  
static double Max=0.9; //方波函数的最大值  
static double Min=0.1; //方波函数的最小值  
  
typedef double Func(double);  //定义一个函数类型 Func*为函数指针  
typedef void Solve(Func *calc);//定义函数类型,參数为函数指针Func* 

inline DWORD get_time()   
{   
    return GetTickCount(); //操作系统启动到如今所经过的时间ms  
}  

double calc_sin(double x)  //调用周期函数solve_period的參数  
{    
    return (1 + sin(FACTOR * x)) / 2; //y=1/2(1+sin(a*x))  
}  
double calc_fangbo(double x)  //调用周期函数solve_period的參数  
{  
    //方波函数  
    if(x<=PERIOD/2) return Max;  
    else return Min;  
}  
  
void solve_period(Func *calc) //线程函数为周期函数  
{  
    double x = 0.0;  
    double cache[COUNT];  
    for (int i = 0; i < COUNT; ++i, x += GAP)   
        cache[i] = calc(x);   
    int count = 0;  
    while(1)  
    {  
        unsigned ta = get_time();  
        if (count >= COUNT) count = 0;  
        double r = cache[count++];  
        DWORD busy = r * GAP;  
        while(get_time() - ta < busy) {}  
        Sleep(GAP - busy);  
  }  
}  
  
void solve_linear(Func*)  //线程函数为线性函数,參数为空 NULL  
{  
    const unsigned BUSY =  Ratio * GAP_LINEAR;  
    const unsigned IDLE = (1 - Ratio) * GAP_LINEAR;  
    while(1)  
    {  
        unsigned ta = get_time();  
        while(get_time() - ta < BUSY) {}  
        Sleep(IDLE);  
    }  
}  
  
void run(int i=1,double R=0.5,double T=60000,double max=0.9,double min=0.1)  
	//i为输出状态,R为直线函数的值,T为周期函数的周期,max方波最大值,min方波最小值  
{  
	Ratio=R; PERIOD=T; Max=max; Min=min; 
	Func *func[] = {NULL ,calc_sin,calc_fangbo};  //传给Solve的參数。函数指针数组  
	Solve *solve_func[] = { solve_linear, solve_period};  //Solve函数指针数组  
	SYSTEM_INFO info;  
	GetSystemInfo(&info);   //得到cpu数目  
	int NUM_CPUS = info.dwNumberOfProcessors;
	HANDLE *handle = new HANDLE[NUM_CPUS];    
	DWORD *thread_id = new DWORD[NUM_CPUS]; //线程id  

	switch(i)  
	{  
	case 1: //cpu0 ,cpu1都输出直线  
		{  
			for (int i = 0; i < NUM_CPUS; ++i)  
			{  
				Func *calc = func[0];  
				Solve *solve = solve_func[0];  
				if ((handle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,   
					(VOID*)calc, 0, &thread_id[i])) != NULL)  //创建新线程  
					SetThreadAffinityMask(handle[i], i); //限定线程执行在哪个cpu上  
			}  
			WaitForSingleObject(handle[0],INFINITE);   //等待线程结束  
			break;  
		}  
	case 2: //cpu0直线,cpu1正弦  
		{  
			if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[0],   
				(VOID*)func[0], 0, &thread_id[1])) != NULL)  //创建新线程  
				SetThreadAffinityMask(handle[1], 1); //限定线程执行在哪个cpu上   
			Func *calc = func[1];  
			Solve *solve = solve_func[1];  
			if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,   
				(VOID*)calc, 0, &thread_id[0])) != NULL)  //创建新线程  
				SetThreadAffinityMask(handle[0], 2); //限定线程执行在哪个cpu上 
			WaitForSingleObject(handle[0],INFINITE);   //等待线程结束  
			break;  
		}  
	case 3: //cpu0直线,cpu1方波  
		{  
			if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[0],   
				(VOID*)func[0], 0, &thread_id[0])) != NULL)  //创建新线程  
				SetThreadAffinityMask(handle[0], 1); //限定线程执行在哪个cpu上  
			Func *calc = func[2];  
			Solve *solve = solve_func[1];  
			if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,   
				(VOID*)calc, 0, &thread_id[1])) != NULL)  //创建新线程  
				SetThreadAffinityMask(handle[1], 2); //限定线程执行在哪个cpu上  
			WaitForSingleObject(handle[0],INFINITE);   //等待线程结束  
			break;  
		}  
	case 4: //cpu0正弦。cpu1方波  
		{  
			if ((handle[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve_func[1],   
				(VOID*)func[1], 0, &thread_id[0])) != NULL)  //创建新线程  
				SetThreadAffinityMask(handle[0], 1); //限定线程执行在哪个cpu上  
			Func *calc = func[2];  
			Solve *solve = solve_func[1];  
			if ((handle[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)solve,   
				(VOID*)calc, 0, &thread_id[1])) != NULL)  //创建新线程  
				SetThreadAffinityMask(handle[1], 2); //限定线程执行在哪个cpu上  
			WaitForSingleObject(handle[0],INFINITE);   //等待线程结束  
			break;  
		}  
	default: break;  
	}  
}  
  
void main()  
{  
    //run(1,0.5);  //cpu1 ,cpu2都输出50%的直线  
   run(2,0.5,30000); //cpu1 0.5直线,cpu2正弦周期30000  
    //run(3);  //cpu1直线,cpu2方波  
    //run(4,0.8,30000,0.95,0.5); //cpu1正弦。cpu2 0.95-0.5的方波  
}  

这是是第一次CPU跑直线。第二个CPU跑正弦函数~~~~~~~~·




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值