上个月到福州路“扫”书店,本来想买几本敏捷和TDD的书,确意外发现了《编程之美》这本书。一开始被吸引是因为书名下面的副标题:微软面试。。。。,因为2005年毕业的时候曾有一次失败的微软面试,所以抱着事后看客的心态拿起来翻翻,这一翻就决定立刻买下这本书。从第一章开始,每一章的例子都很吸引我,虽然有一些题目早知道解法,但看到有更巧妙的方法时还是忍不住击节,要是但是面试的时候能先看看这本书就好了,嘿嘿。夸归夸,缺点还是要提的,本书的勘误之多也是出类拔萃的,拿到书时就看到里面夹了一张勘误表,当时没在意,到看的时候就发现不对劲了,上网一查,原来还有一张更长的勘误表,呵呵。
书中第9页给出了一个能够根据系统性能动态自适应的解法,就是那个MakeUsage()函数,原文代码清单如下:
// C# code
static void MakeUsage(float level)
{
PerformanceCounter p = new PerformanceCounter(“Processor”,”% Processor Time”, “_Total”);
while(true)
{
if(p.NextValue() > level)
System.Threading.Thread.Sleep(10);
}
}
勘误表中对这个函数也没有修改,只是增加了对p是否有效的判断
if(p==NULL)
{
Return;
}
书上说上面的解法能够处理各种CPU使用率参数,不过看起来好像不对劲,“_Total”参数表示那个PerformanceCounter p的值应该是当前系统中所有CPU的占用时间,是一个long型整数,如果当前系统idle时间是96%的话,这个PerformanceCounter p的值就应该是4。代码的本意是当CPU占用小于level时通过循环提高CPU占用时间,当CPU占用超过level时,就Sleep(10)。不过从代码上看应该是起不到应有的效果,因为只要一个循环就可能将CPU的占用提高到100,而Sleep(10)时CPU的占用是0,最终的结果应该只在50和100两个使用率上是准确的。我手上没有合适的C#编译器,不过我用C++改写了这个函数:
LPCTSTR CounterName = _T(“//Processor(0)//% Processor Time”);
void MakeUsage(long percent)
{
if(percent < 0 || percent > 100)
return;
CPdhQuery query;
CPdhCounter counter(CounterName);
query.OpenH(NULL);
query.Add(counter);
PDH_FMT_COUNTERVALUE fv;
while(true)
{
query.Collect();
counter.GetFormattedValue(PDH_FMT_LONG, fv);
//_tprintf(_T(“cpu time %d/n”), fv.longValue);
if(fv.longValue > percent)
Sleep(10);
}
query.Remove(counter);
query.Close();
}
Processor我用了0号processor,因为我写这个代码用的机器只有一个单核CPU。如果改成:
LPCTSTR CounterName = _T(“//Processor(_Total)//% Processor Time”);
效果是一样的。CPdhQuery类和CPdhCounter类用的是PJ Naughter的代码,可以从http://www.naughter.com/下载CPdh类。我测试的结果如果percent是100,则CPU占用可以稳定在100,对于其它值都是稳定在50左右,也就是说percent参数是无效的,不知道C#的结果如何,有时间再试一下。
另外,所有的例子都是使用Sleep释放CPU占用,但是从 Windows NT(包括2K和XP) 里获得的时间戳(Timestamp),其最高精度为 10 到 15 毫秒,具体是多少视你机器上的硬件时钟决定,可以用下面一段函数查看你的Windows所能提供的时钟精度:
SYSTEMTIME st;
while(TRUE)
{
::GetSystemTime(&st);
_tprintf(_T(“Current Time : %02d:%02d:%02d.%03d/n”),
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
}
这是在某台单核处理的机器上输出结果是:
Current Time : 14:57:49.314
Current Time : 14:57:49.314
Current Time : 14:57:49.314
Current Time : 14:57:49.314
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.324
Current Time : 14:57:49.334
Current Time : 14:57:49.334
可见,不管循环有多快,这个系统能够提供的精度是10ms,也就是系统时间更新频率是10ms一次,(如果是多核处理器,别忘了调用SetProcessAffinityMask)很多API都依赖于这样一个10ms精度的系统时间,包括Sleep函数,所以想要Sleep函数提供ms级别的精度是不现实的。
书中对多核CPU没有给出实际的例子,不过结合SetThreadAffinityMask和GetProcessorInfo可以很容易实现,就是对每个CPU启动一个线程,然后在线程内调用SetThreadAffinityMask:
#define CPU_BIT_MASK 0x00000001
DWORD mask = CPU_BIT_MASK < cpu_no;
SetThreadAffinityMask(GetCurrentThread(), mask);
其中cpu_no是CPU编号,从0开始。