Author: Fox
前两天在买《计算机程序设计艺术 》中文版的时候,偶然发现《编程之美 》这本书,当时翻了一下,看到“让CPU占用率曲线听你指挥 ”这样的题目确实让人为之一动。写一段代码,可以让CPU占有率曲线画出平滑的正弦曲线,有点意思:-)。
当然,最后没有买这本书,虽然我可以肯定这是本好书。
我从优快云读书 上找到几节,闲来读一读。今天来讨论一下《让CPU占用率曲线听你指挥 》。
题目:写一个程序,让用户来决定Windows任务管理器(Task Manager)的CPU占用率。程序越精简越好,计算机语言不限。例如,可以实现下面三种情况:
1. CPU的占用率固定在50%,为一条直线;
2. CPU的占用率为一条直线,但是具体占用率由命令行参数决定(参数范围1~ 100);
3. CPU的占用率状态是一个正弦曲线。
在讨论具体实现之前,一个非常重要的问题是:什么是CPU占用率 ?
《编程之美 》写道:“在任务管理器的一个刷新周期内,CPU忙(执行应用程序)的时间和刷新周期总时间的比率,就是CPU的占用率,也就是说,任务管理器中显示的是每个刷新周期内CPU占用率的统计平均值。 ”
打开“Windows 任务管理器”,“性能”中有“CPU使用记录”一项,给出的就是CPU占有率曲线。
至于一个刷新周期到底是多长,书中 似乎没有明确给出,只是说“大约是1秒钟更新一次”,我打开Windows自带的时钟,也感觉大约是1秒钟。
另外的常识是:
单核环境下,空死循环会导致100%的CPU占有率。双核环境下,CPU总占有率大约为50%,四核会不会是25%左右呢?(我没有四核,只能猜测了,估计各核间切换也会耗掉点时间,因为我的双核环境并没有出现一核100%,另一核空闲的情况)。
当CPU整个刷新周期(绝大多数时间)空闲时,CPU占有率趋于0。
书中 给出的正弦实现如下:
2 #include " stdlib.h "
3 #include " math.h "
4
5 const double SPLIT = 0.01 ;
6 const int COUNT = 200 ;
7 const double PI = 3.14159265 ;
8 const int INTERVAL = 300 ;
9
10 int _tmain( int argc, _TCHAR * argv[])
11 {
12 DWORD busySpan[COUNT]; // array of busy times
13 DWORD idleSpan[COUNT]; // array of idle times
14 int half = INTERVAL / 2 ;
15 double radian = 0.0 ;
16 for ( int i = 0 ; i < COUNT; i ++ )
17 {
18 busySpan[i] = (DWORD)(half + (sin(PI * radian) * half));
19 idleSpan[i] = INTERVAL - busySpan[i];
20 radian += SPLIT;
21 }
22 DWORD startTime = 0 ;
23 int j = 0 ;
24 while ( true )
25 {
26 j = j % COUNT;
27 startTime = GetTickCount();
28 while ((GetTickCount() - startTime) <= busySpan[j]) ;
29 Sleep(idleSpan[j]);
30 j ++ ;
31 }
32 return 0 ;
33 }
在单核环境(P4 2.40)下,其表现还是不错的:
在双核环境(Core2 E4500)下,就有点差强人意 不尽人意了:
不过,总还能看出是正弦曲线。
上面两图的问题:
1) 单核时曲线不够平滑,是由于QQ等程序占用CPU所致;
2) 双核时曲线更加抖动,我的理解是除其他程序影响外,由于线程没有固定运行在一个CPU上导致的,后面看到书上 提到线程迁移 ,个人感觉这个叫法欠妥啊,总觉得线程迁移 令人费解。
可以立即想到的是:让进程在指定处理器上运行(处理器亲缘关系) ,由Windows提供了两个API可以做到这一点:GetCurrentProcess 和SetProcessAffinityMask 的。
修改之后的代码如下:
2 #include " stdlib.h "
3 #include " math.h "
4
5 const double SPLIT = 0.01 ;
6 const int COUNT = 200 ;
7 const double PI = 3.14159265 ;
8 const int INTERVAL = 300 ;
9
10 int _tmain( int argc, _TCHAR * argv[])
11 {
12 SetProcessAffinityMask (
13 GetCurrentProcess (),
14 0x00000001 //cpu mask
15 );
16
17 DWORD busySpan[COUNT]; // array of busy times
18 DWORD idleSpan[COUNT]; // array of idle times
19 int half = INTERVAL / 2 ;
20 double radian = 0.0 ;
21 for ( int i = 0 ; i < COUNT; i ++ )
22 {
23 busySpan[i] = (DWORD)(half + (sin(PI * radian) * half));
24 idleSpan[i] = INTERVAL - busySpan[i];
25 radian += SPLIT;
26 }
27 DWORD startTime = 0 ;
28 int j = 0 ;
29 while ( true )
30 {
31 j = j % COUNT;
32 startTime = GetTickCount();
33 while ((GetTickCount() - startTime) <= busySpan[j]) ;
34 Sleep(idleSpan[j]);
35 j ++ ;
36 }
37 return 0 ;
38 }
双核环境(Core2 E4500)修改之后的输出如下:
我理想中的表现是:
1) 曲线是平滑的,最好不因其他应用程序或操作的执行而改变;
2) 不管是单核还是双核,峰值皆为100%,谷值为0。
对于第一点,其实就是保证任一刷新周期中的CPU占有率都可以被精确控制在0-100之间,如果你可以使CPU一直保持50%(而不是近似的上下波动),产生一条平滑的曲线就很easy了。
问题的关键在于,除了当前你写的程序可以控制,其他程序或操作如何控制?或者说:如何控制CPU的运行情况才是关键之处 。
PS: 一晚上老是断网,搞得思路频频被打断,兴致也损了大半。总之,《编程之美 》还是值得玩味一把吧:D。
原帖地址:http://www.cppblog.com/Fox/archive/2010/12/26/47343.html
《编程之美》:http://book.douban.com/subject/3004255/