让多核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%直线,程序代码为:
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)对于绘制正弦曲线:
通过在一个周期2*PI中等分200份,将每一个间隔点的half + (sin( PI * radian) * half))的值存入busySpan[i],将其补植存入idleSpan[i]。half是整个值域INTERVAL的一半。这样可以近似趋近一条正弦曲线。
运行效果为:
(4)可以通过RDTSC指令获得当前CPU核心运行周期数。
在x86平台上定义函数:
在x64平台上定义:
使用CallNtPowerInformation API得到CPU频率,从而将周期数转化为毫秒数,例如如下:
RDTSC指令读取当前CPU的周期数,在多CPU系统中这个周期数在不同的CPU间基数不同,频率也不同。用从两个不同的CPU得到的周期数来计算会得出没有意义的值。所以需要用SetProcessAffinityMask避免进程迁移。另外,CPU的频率也会随系统供电及负荷情况有所调整。
More:
如何在Linux下如Ubuntu中的系统监视器中实现绘制正弦曲线?请听下回分解。