让多核CPU占用率曲线听你指挥(Windows实现)——《编程之美》1.1学习笔记

让多核CPU占用率曲线听你指挥——《编程之美》1.1学习笔记

Problem:   

写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率。有以下几种情况:

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

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

3.CPU的占用率状态为一条正弦曲线。

分析与解法:

(1)通过观察任务管理器,它大约1s更新一次。当CPU使用率为0时,System Idle Process占用了CPU的空闲时间。

System Idle Process在CPU空闲的的时候,发出一个IDLE命令,使CPU挂起(暂时停止工作),可有效的降低CPU内核的温度,无法终止。在这个进程里出现的CPU占用数值并不是真正的占用而是体现的CPU的空闲率,也就说这个数值越大CPU的空闲率就越高,反之就是CPU的占用率越高。Linux中对应的进程为init,PID为1。

 

当系统中的进程或者在等待用户输入,或者在等待某些事件的发生(发出I/O请求等待I/O响应),或者主动进入休眠状态(比如Sleep())。

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

 

书上的代码以单核CPU为前提,但对于多核CPU来说,同一个进程可能被CPU的任务分配器分配到不同的核心上执行,所以造成无法让任务管理器达到预想的效果。其实打开任务管理器,可以看到多个CPU使用记录。本人电脑CPU是Core i5 450M,双核4线程。在OS看来就如同有四个CPU工作一样。我的任务管理器中就有四个CPU使用记录。

所谓超线程技术就是利用特殊的硬件指令,把多线程处理器内部的两个逻辑内核模拟成两个物理芯片,从而使单个处理器就能“享用”线程级的并行计算的处理器技术。多线程技术可以在支持多线程的操作系统和软件上,有效的增强处理器在多任务、多线程处理上的处理能力。

 

可以使用SetProcessAffinityMask()函数可以使特定的处理器运行指定进程。

BOOL SetProcessAffinityMask(HANDLE hProcess, DWORD_PTR dwProcessAffinityMask);

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

DWORD_PTR,其实就是unsigned long*.Unsigned long type for pointer precision.Use 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)

 

        以此类推。。。

 

 

HANDLE GetCurrentProcess(void);

可以获得当前进程的句柄。注意,这个句柄为一个伪句柄。只能在我们的进程中才能代表当前进程的句柄,事实上这个函数目前只是简单的返回-1这个值。也就是说在我们的程序中-1便能表示本进程的句柄。

(2)那么对于绘制50%直线,程序代码为:

 

 

#include <Windows.h> #include<stdlib.h> #include<tchar.h> int _tmain(int argc,_TCHAR* argv[]) { int busyTime = 10; int idleTime = busyTime*5; __int64 startTime = 0; ::SetThreadAffinityMask(::GetCurrentProcess(),0x00000001); while(true) { startTime = GetTickCount(); //busy loop while((GetTickCount() - startTime) <= busyTime); //idle loop Sleep(idleTime); } return 0; }

GetTickCount()可以得到系统从启动到运行到现在所经历时间的毫秒值。最多能统计到49.7天。我们利用它判断busy loop要持续多久。

其中idleTime为busyTime的五倍,可以修改其值使其更逼近50%。不同机子的情况不同。

__int64是VC++的64位扩展。范围为[-2^63,2^63)。当64位与32位混合运算时,32位整数会隐式转换成64位整数。输入输出它时使用cin、cout会造成错误。需要使用scanf("%I64d",&a);和printf("%I64d",a);

还有unsigned __int64,其范围为[0,2^64)。

对应g++中的64位扩展为long long和unsigned long long。范围与运算与上相仿。输入输出使用scanf("%lld",&a);和printf("%lld",a);

 

int _tmain(int argc, _TCHAR* argv[])。

_tmain这个符号多见于VC++创建的控制台工程中,这个是为了保证移植unicode而加入的(一般_t、_T、T()这些东西都和unicode有关系)。定义在头文件tchar.h中。

(3)对于绘制正弦曲线:

 

#include <Windows.h> #include<stdlib.h> #include<math.h> #include<tchar.h> const double SPLIT = 0.01; const int COUNT = 200; const double PI = 3.14159265; const int INTERVAL = 300; int _tmain(int argc, _TCHAR* argv[] ) { DWORD busySpan[COUNT]; //array of busy times DWORD idleSpan[COUNT]; //array of idle times 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; ::SetProcessAffinityMask(::GetCurrentProcess(),0x00000002); while(true) { j = j % COUNT; startTime = GetTickCount(); while((GetTickCount() - startTime) <= busySpan[j]); Sleep(idleSpan[j]); j++; } return 0; }

通过在一个周期2*PI中等分200份,将每一个间隔点的half + (sin( PI * radian) * half))的值存入busySpan[i],将其补植存入idleSpan[i]。half是整个值域INTERVAL的一半。这样可以近似趋近一条正弦曲线。

运行效果为:

(4)可以通过RDTSC指令获得当前CPU核心运行周期数。

在x86平台上定义函数:

inline __int64 GetCPUTickCount() { __asm { rdtsc; } }

在x64平台上定义:

#define GetCPUTickCount() __rdtsc()

使用CallNtPowerInformation API得到CPU频率,从而将周期数转化为毫秒数,例如如下:

_PROCESSOR_POWER_INFORMATION info; CallNTPowerInformation(11, //query processor power information NULL, //no input buffer 0, //input buffer size is zero &info, //output buffer sizeof(info) //outbuf size ); __int64 t_begin = GetCPUTickCount(); //do something __int64 t_end = GetCPUTickCount(); millisec = ((double)t_end - (double)t_begin)/(double)info.CurrentMhz; 

 

RDTSC指令读取当前CPU的周期数,在多CPU系统中这个周期数在不同的CPU间基数不同,频率也不同。用从两个不同的CPU得到的周期数来计算会得出没有意义的值。所以需要用SetProcessAffinityMask避免进程迁移。另外,CPU的频率也会随系统供电及负荷情况有所调整。

More:

如何在Linux下如Ubuntu中的系统监视器中实现绘制正弦曲线?请听下回分解。

你可能感兴趣的:(多线程,编程,windows,Parameters,任务,casting)