天天看点

精确测量程序运行时间

   转载请注明出处:http://blog.csdn.net/qingheuestc, by EinsteinInIct。

   作为一个程序员,很多时候都会去关心自己的程序跑的到底有多快,这时就需要对程序的运行时间进行测量。对于不同的程序,它们的运行时间可能相差很大。诸如科学计算类的程序,由于计算量大且算法的并行化比较差,可能几天都跑不完。而有的程序的运行时间可能只有几分钟甚至不到1ms。不同的时间测量方法,其测量精度和实现难度均有所不同。正因如此,我们要学会选择合适的方法,来满足对测量精度的不同需求。下面就按照测量精度递增的顺序简单介绍几种方法(精度范围:s~ms~us)

   1,脚本实现(精度:s)

   在windows下面,可以在程序开始前将当前时间用"time /T >time.txt"命令,将程序的启动时间存到time.txt文件中,然后在程序结束后再用"time /t >>time.txt"将当前时间追加到文件的第二行。这样,两次的时间值相减即可得到程序的运行时间差。当然,如果程序的运行时间跨越了凌晨12:00,可以考虑配合"date /t"命令一起使用。这样即使你跑个几年也不会有问题

精确测量程序运行时间

   在linux下面,这个工作就更简单了,直接"time $command"就能看到运行时间了。         

   分析:正常情况下,此方法的误差应该不超过2s,因此如果你的程序的运行时间远大于2s,可以考虑用此方法。

   2,利用语言或者系统提供的api。(精度:ms)

   在c语言中,可以用如下方法来获得程序的运行时间:

        clock_t start = clock();

        yourCodeHere();

        clock_t end = clock();

        printf("time cost is : %lf", (double)(end - start) / CLOCKS_PER_SEC);

    在windows中,也提供了相关的api,如GetTickCount()等。

    分析:精度可以达到毫秒级,它们读取的是bios里面的real time值,或者叫做墙上时钟(ps:bios里面有个专门的寄存器,用来记录从1970年到现在为止的毫秒值,并且有一块单独的电池为此时钟进行供电,因此即使掉电这个时钟也照样跑)

   3,用单独的汇编指令序列来实现(精度:us甚至几十ns)

   对于每一个好奇的程序员或者是十分关心代码性能的程序员来说,他绝对不会满足于毫秒级的时间准确度。他们甚至想去测量自己的某几行甚至是一行指令花了多少时间,而这些时间一般都是微秒级的。下面我们就尝试着做这样一件事。

   在x86的处理器里面,有一个寄存器叫time stamp counter,它在系统启动时被初始化为0并在每个cycle的时候加1。因此通过2次读取该寄存器的值,并求其差值我们就可以粗略得到程序的运行时间。说粗略主要是因为我们假设代码运行期间不被打断,而且在深度流水且乱序执行的处理器上,想要通过跑代码这种物理方法来得到指令的准确运行时间是不可能的。下面就是这种方法的代码实现:

rdtsc

mov [start_low],eax

mov [start_high],edx

xor eax,eax

cpuid

your_code_here

xor eax,eax

cpuid

rdtsc

mov [end_low],eax

mov [end_high],edx

   分析:rdtsc用于读取time stamp counter寄存器的值并将其放在edx:eax中,后面的两条指令用来保存edx和eax的值。由于处理器是乱序执行的,所以必须防止被测代码跑到rdtsc之前执行。通过在真实代码之前加入串行cpuid指令,可以起到排空流水线的功能,从而使后面的代码在rdtsc之后执行。在执行cpuid之前,之所以要先清eax,是防止由于eax不同而造成cpuid这条指令的执行时间的不确定性。这样,再配合下面的C代码就能得到程序的运行时间:

(end_high<<32)+end_low-(start_high<<32)-end_high ,就能得到运行时间了。

说明:

   1,此方法计算得到的实际上是代码的运行所花的cycles,而并不是时间,如果要测时间,还须再去除以处理器的主频。如果处理器的主频变了,这样测得的时间将没有任何意义。不过,很多时候,貌似cycles比时间更具参考意义。

   2,此方法测得的也并非严格的cycles,因为在两次rdtsc之间不仅加入了内存操作指令,而且还有两次的cpuid和xor eax,eax。即使我们做了不少努力来减少误差,但不得不说我们这样的代码也着实有点“变态”,试问谁没事写代码的时候会去主动排空流水线,想塞满还来不及呢。换句话说,我们所使用的手段已经让被测代码跟实际应用中的场景产生了不少距离。用量子力学中的一句话来说,针对物体的任何测量都不可避免的引起该物体的变化。

   3,此方法的测量时间误差大约在几十个cycles(也即几十个ns),因此不适合单条指令时间的测量,但是对于us级的代码应该还是没问题的。

   4,在amd的处理器上,time stamp counter的值貌似不是每个cycle增加一次,我的Turion TL-60测量结果貌似是2个cycle增加一次。

   对于时间的测量,看似是个简单的问题,但是要想把它做得那么尽善尽美似乎也没那么容易。另外,在实际应用中,一定要根据自己的需要选择最经济实惠的方法。

参考资料:

1,《code optimization:有效使用内存》by Kris Kapersky。

2,百度搜索

实力有限,欢迎拍砖。

继续阅读