《编程之美》之CPU曲线

1.CPU相关:(摘自MSDN——Processor Groups

Systems with more than one physical processor or systems with physical processors that have multiple cores provide the operating system with multiple logical processors. A logical processor is one logical computing engine from the perspective of the operating system, application or driver. A core is one processor unit, which can consist of one or more logical processors. A physical processor can consist of one or more cores. A physical processor is the same as a processor package, a socket, or a CPU.

从操作系统(或者是应用程序、驱动)的角度看,一个逻辑处理器就是一个逻辑计算引擎。一个内核就是一个处理单元,它可以由一个或多个逻辑处理器组成。一个物理处理器可以由一个或多个内核组成。

以我的电脑为例,CPU为i3-2330M,2.2GHz,2个内核,4个逻辑处理器(也叫做双核四线程)。2个内核拥有2*2个逻辑处理器,得益于Intel的超线程技术(HT),该技术可实现在一个实体处理器中提供两个逻辑线程,也就是说一个处理器可模拟出两个处理器。在Windows任务管理器里就可以体现出来。可以看出,有4个小窗口。

注意:若其中一个逻辑处理器满负荷运转,则CPU的使用率为25%,若控制一个逻辑处理器的使用率为50%,则CPU的使用率为12.5%。



2.直线CPU

要控制CPU的使用率,就必须了解一个函数——Sleep(),该函数的作用是让程序让出CPU时间,除了Sleep,其他的操作都会占用CPU时间。

①根据CPU速度估算

i3-2330M的速度达到2.2GHz,一个for循环for(i=0;i<n;i++)转换成汇编语句:

loop:

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

inc dx;将dx寄存器加1

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

cmp i n;比较i和n

jl loop;i小于n时则重复循环

共5句汇编,现代CPU每个时钟周期可执行两条以上的指令,那么每秒可执行for循环的次数为2.2G*2/5次,执行代码如下:

void LineCPU1()
{
	SetThreadAffinityMask(GetCurrentThread(), 8); 
	long i;
	while(1)
	{
		for(i=0;i<8800000;i++);
		Sleep(10);
	}
}
这里选用10ms是因为10ms比较接近Windows调度时间片,若该值太大,会使得曲线不平滑,一下很高,一下很低;若该值太小,会使得线程被频繁地唤醒和挂起,无形中又增加了内核时间不确定性的影响。

从执行效果可看到,该程序(test2.exe)CPU使用率为18%,大于我们希望的12.5%,我将n改为4400000,使用率会低至14%,但还是不是12.5%。可见这种方法比较粗糙,且只适用于特定频率的CPU,具有CPU差异性。(笔记本出于省电的考虑,CPU的时钟频率可能会变化)

②利用GetTickCount()消除CPU差异性

GetTickCount()函数可以得到“系统启动到现在”所经历的毫秒数,最多可统计到49.7天,执行效果:平均CPU使用率基本可维持在12.5上下

void LineCPU2()
{
	SetThreadAffinityMask(GetCurrentThread(), 8);
	int busyTime = 100;
	int idleTime = busyTime;
	long startTime = 0;
	while(1)
	{
		startTime = GetTickCount();
		while((GetTickCount() - startTime) <= busyTime);
		Sleep(idleTime);
	}
}

以上两种方法都是假设只有该进程运行在逻辑处理器上,但事实上系统运行时会有其他进程也在运行,若其他进程占用了5%,那么我们的程序就应该占用45%。那么如何解决这个问题呢。

③PerformanceCounter类

PerformanceCounter是.Net类库中的一个类(通过它可以很方便地获取当前的各种性能数据,包括CPU使用率),所以需要用C#来写,需要命名空间using System.Diagnostics,该程序没怎么具体去试

PerformanceCounter pc = new PerformanceCounter("Processor", "% Processor Time", "_Total");       
while (true)
{
    if (pc.NextValue() > 18)
        System.Threading.Thread.Sleep(10);
}

3.正弦CPU

有了以上的铺垫,那么正弦的CPU曲线就很容易啦

void SinCPU()
{
	SetThreadAffinityMask(GetCurrentThread(), 8);  
	const double SPLIT = 0.01;
	const int COUNT =200;
	const double PI = 3.14159265;
	const int INTERVAL = 300;

	int busySpan[COUNT];
	int idleSpan[COUNT];
	int half = INTERVAL/2;
	double rad =0.0;
	for(int i=0;i<COUNT;i++)
	{
		busySpan[i] = (int)(half+(sin(PI*rad)*half/2));
		idleSpan[i] = INTERVAL - busySpan[i];
		rad += SPLIT;
	}

	long startTime = 0;
	int j = 0;
	while(1)
	{
		j = j%COUNT;
		startTime = GetTickCount();
		while((GetTickCount() - startTime)<=busySpan[j]);
		Sleep(idleSpan[j]);
		j++;
	}
}



4.指定某个处理器上运行

SetThreadAffinityMask(hThreaddwThreadAffinityMask)函数的作用是指定该线程在某一个逻辑处理器上运行,每个处理器代表1bit,即1代表第1个处理器,2代表第2个处理器,4代表第3个处理器,8代表第4个处理器,从下面两张图可看出区别,一个是完整的正弦,一个是有缺口的正弦


当第一个正弦有缺口时,第二、第三个处理器有出现相应的峰值,可见当一个程序在某个处理器中接近满负荷时,会被分配到另外几个处理器中,以减小处理器的压力


注:SetThreadAffinityMask(hThreaddwThreadAffinityMask)、Sleep()、GetTickCount()函数需要"Windows.h"头文件,数学函数sin()需要"math.h"头文件,CPU的频率会随着供电和负载情况的变化而变化。

posted @ 2014-05-03 17:13  水煮海鲜  阅读(558)  评论(0编辑  收藏  举报