编程之美 — 让CPU占用率绘制任意图形
《编程之美》第一章给出了一个面试题:
写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率,程序越精简越好,计算机语言不限。例如,你可以实现下面三种情况:
1.CPU的占用率固定在50%,为一条直线;
2.CPU的占用率为一条直线,但具体占用率由命令行参数决定(参数范围1~100);
3.CPU的占用率状态是一条正弦曲线。
最一般的思路:CPU占用率为一条直线,首先要搞清楚什么是CPU占用率,CPU占用率是你运行的程序占用的CPU资源,表示你的机器在某个时间点的运行程序的情况。其实在某一时间点,CPU要么被占用,要么没被占用,即占用率要么为1,要么为0;那为什么还有占用率为50%之说呢,其实这里的时间指的一段时间,这个时间对人来可能就是滴答一下就流逝了,人的感觉可能是一个时间点,但对于机器来说,这就是一个时间段,时间周期。这样就好理解了,在一段时间内,CPU占用率就是CPU被占用的时间除以这段时间的总时间,即:
CPU占用率 = CPU被占用的时间 / 总时间
在《编程之美》中已经给出了根据CPU主频计算for(int i = 0; i < n; i++)循环n的大小,我的电脑的主频是2.00GHZ,所以for里面运行1s才跳出的n的大小为:
2 × 109 × 2 ÷ 5 = 8 × 108,其中后面乘以2表示CPU每个时钟周期执行两条代码,而for(int i = 0; i < n; i++);转成汇编是5条,所以除以5。
为了接近Windows的调试时间片,取Sleep(10),那么此时,n取8000000。
具体程序如下:
#include <iostream> #include <windows.h> using namespace std; int main() { while(true) { for(int i = 0; i < 8000000; i++) { ; } Sleep(10); } return 0; }
运行结果:
结果几近一条直线,但有较大的波动,特别是你还在运行其它程序时,比如拖到鼠标,波动会更大。
上面计算存在许多近似:n计算的近似;以for()循环汇编代码的条数代替CPU时钟周期执行的代码数等等。
可利用GetTickCount()更精确获取系统时间,GetTickCount()获取的是系统到“现在”所经历时间的毫秒数。
#include <iostream> #include <windows.h> using namespace std;
int main() { _int64 start_time = 0; int run_time = 10; int sleep_time = run_time; while(true) { start_time = GetTickCount(); while((GetTickCount() - start_time) <= run_time); Sleep(sleep_time); } return 0; }
运行结果:
从结果可以看出,直线的波动较小,GetTickCount()可获取更精确的系统时间。
下面来让CPU占用率绘制正弦曲线:
下面是《编程之美》上的代码:
#include <Windows.h> #include <stdlib.h> #include <math.h> const double SPLIT = 0.01; const int COUNT = 200; const double PI = 3.13159265; const int INTERVAL = 300; int main() { DWORD busySpan[COUNT]; DWORD idleSpan[COUNT]; 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; }
运行结果:
这里主要讲下下面代码的意思:
for(int i = 0; i < COUNT; i++) { busySpan[i] = (DWORD)(half + (sin(PI * radian) * half)); idleSpan[i] = INTERVAL - busySpan[i]; radian += SPLIT; }
我们知道绘制一条正弦曲线,只要知道0~2∏之间的CPU占用率的值就行了,其它就是周期移动就行了。
题目要求CPU占用率为正弦曲线,所以不仿设busySpan[i] / (busySpan[i] + idleSpan[i]) = asin(∏ti + φ) + b;
还知道busySpan[i] / (busySpan[i] + idleSpan[i]) + idleSpan[i] / (busySpan[i] + idleSpan[i]) = 1,这里(busySpan[i] + idleSpan[i])等于INTERVAL。综合上面两个方面,可取φ = 0;a = b = INTERVAL/2。
∏ × SPLIT × COUNT = 2∏,正好为正弦函数的一个周期。
扩展:如何让CPU占用率为半圆形曲线
参考程序如下:
#include <windows.h> #include <stdlib.h> #include <math.h> const double SPLIT = 0.01; const int COUNT = 200; const int INTERVAL = 300; int main() { DWORD busySpan[COUNT]; DWORD idleSpan[COUNT]; double radian = 0.0; for(int i = 0; i < COUNT; i++) { busySpan[i] = (DWORD)(INTERVAL * sqrt(1 - pow((1 - radian) , 2))); 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; }
运行结果:
当然你还可以让它绘制出更漂亮的图形。