测试 、优化程序性能——特别是为 Windows 服务器 开发的多线程程序,操作系统提供的标准时钟是不够的,必须使用解析度更高的时钟。本文介绍了如何访" name="description" />
typedef struct _BinInt32 { __int32 i32[2]; } BigInt32; typedef struct _BigInt64 { __int64 i64; } BigInt64; typedef union _bigInt { BigInt32 int32val; BigInt64 int64val; } BigInt; |
BigInt start_ticks, end_ticks; _asm { RDTSC mov start_ticks.int32val.i32[0], eax mov start_ticks.int32val.i32[4], edx } |
printf ( "Function used %I64Ld ticks\n", end_ticks.int64val.i64 - start_ticks.int64val.i64 ); |
Win32函数QueryPerformanceCounter()的功能也大致相似,它唯一的参数是一个指向计数器变量的长指针。如果函数调用失败,它返回0(实际上是FALSE)。然而,上面提供的代码突破了Windows调用的黑箱,即使在非Windows的Intel系统上,也能发挥同样的功能。
二、使用计时数据
如果要把时钟计数转换成时间,只要把时钟计数除以CPU的时钟频率就可以了。不过,芯片上标准的GHz数据往往与实际运行的速度不同。如果要测试芯片的实际速度,除了Win32调用QueryPerformanceFrequency(),还有几种非常好的工具软件。这里要推荐两种工具,首先是Intel自己的Processor Frequency ID Utility,可以从http://support.intel.com/support/ processors/tools/FrequencyID/FreqID.htm免费下载,它还能提供有关处理器的许多其他信息。另一个工具提供的信息更多,它就是wCPUID,可以从http://www.h-oda.com/免费下载。
这两种工具都能够测出精确的时钟速度,用前面获得的时钟计数除以速度,就可以得到高精度的时间计数。QueryPerformanceFrequency()函数也只有一个长指针参数,出现错误时返回0或FALSE。
在如此高的时钟解析度下,许多平常看不到的现象会显现出来。最令人莫名其妙的是,多次测试同一段代码,结果会出现很大的波动。
大范围波动的主要原因在于读取操作,特别是第一、二两次读取与从缓冲区读取的差异。当代码第一次执行时,一般需要把它装入到缓冲区,代码所操作的数据也一样。用时钟周期来度量,这一缓冲装入过程是相当耗时的。不过,当代码和数据放入了缓冲区(多次运行代码之后的结果),装入缓冲区操作所带来的失真渐渐消失。因此,实际测试时,应当抛弃前几次的数据,只计算结果稳定下来之后的平均值。
然而,即使在看起来比较稳定的结果集中,仍会突然出现一些突变,这是由于操作系统切换线程所导致的。由于时钟计数器总是不停地累加,它的计数不能反映出代码的一部分执行时间已经用于休眠。要解决这个问题,必须将线程设置成Windows最高的优先级,即实时(对应的符号是REALTIME_PRIORITY_CLASS),防止测试期间线程被切换掉。
但是,采用这种解决办法时必须谨慎。如果让一大段代码用这个优先级运行,可能会阻塞其他线程。因此,如果要用这种办法测试大段代码,应当确信系统暂时不作它用。另外,必须记住的是,测试完成后要把代码恢复成标准优先级——注意,是在测试完成后立即恢复,否则的话,可能带来许多风险,例如,可能直到部署应用程序时也不能再想起需要恢复优先级,由此带来的问题可能使用户久久难忘——当你的应用程序开始运行时,其他代码都好像停止运行了。
当然,这是一个可以暂且不管的话题。无论怎样,现在我们已经有了一个高度精确的时钟,它能够在大多数当前的Intel处理器上运行,适合从Windows 95开始的所有Windows操作系统。好好享受吧!