性能测试的时候,一个问题就是时间间隔,有很多时间函数能获取时间,从而求取时间间隔。关于这方面的文章很多,我就不重复了,这里只是提供一些基本的信息,从而方便我自己做性能测试使用:

(1)C语言时间函数clock()和clock_t:

time.h中的c语言时间函数clock()能获取当前时间。需要注意的是,这个函数在Windows和Linux下是通用的,但是其返回值的单位是不同的,一个是毫秒,一个是微秒。

从单位也可以看出该方式获取时间的精度。总体来说,其精度在毫秒级,所以测试性能的时候,对于粗时间间隔的测试,用这个函数就足够了。

需要注意的是:linux上,clock()获取的是CPU时间,不是wall-clock时间,所以如果使用了sleep()等函数,那么是不会计算在内的。但是在Windows平台上,clock()获取的时间包括Sleep()等函数的时间,所以使用clock()的时候要注意这一点,在Linux上得到的很可能不是一个正确的Elapsed time

(2)利用CPU获取高精度时间rdtsc寄存器

rdtsc是一个64位的寄存器,新的CPU都具备这个寄存器,用于记录从计算机启动开始CPU经过的时钟周期,可见其精度之高是和CPU的频率级别的。

可以参考http://blog.sina.com.cn/s/blog_5d9051c00100jcsn.html的文章。

由于是寄存器,所以只要使用汇编读取寄存器的值就可以得到时间了,具体关于汇编的写法有一些不同的形式,具体可以参考网上的写法,而且windows和Linux下也可能是有一些区别。需要注意的是,即使都是在Linux下或都是Windows下,编译64位和32位的时候,其写法也会不一样,参考:http://www.phpzy.com/php/1068620.htmlhttp://www.uplook.cn/index-Index-show-view2681.html?treeid=624(如果错误使用,得到的时间间隔可能为负数)

PS:在Linux上,-m32和-m64选项分别表示生成32位和64位程序,使用-m32会定义__i386__宏,使用-m64则使用__x86_64__宏。

关于Windows上,也可以使用rdtsc寄存器,但是windows上生成64位程序的时候,不知道该如何获得正确值,参考http://sunxiunan.com/?p=983里面有64位的情况,但是只能用于Itanium CPU上。根据MSDN的说明(http://msdn.microsoft.com/en-us/library/ee417693.aspx),不建议使用rdtsc寄存器,建议使用更精确的windows API的QueryPerformanceCounter和QueryPerformanceFrequency。

(3)Windows平台的QueryPerformanceCounter和QueryPerformanceFrequency的使用:

参考:http://www.oschina.net/code/snippet_197161_6789

(4)Timing.h和测试:

下面是能运行于Linux和Windows的代码,可以用于计算程序运行时间,对于一样的性能测试,应该可以胜任。

  1. // Timing.h  
  2. #ifndef TIMING_H  
  3. #define TIMING_H  
  4.   
  5. #include <time.h>  
  6. #include <stdio.h>  
  7.   
  8. #ifdef WIN  
  9. #include <windows.h>  
  10. #define timing_t double  
  11. static _LARGE_INTEGER time_start, time_over;  
  12. static double dqFreq;  
  13.   
  14. static inline void startTiming()  
  15. {  
  16.     _LARGE_INTEGER f;  
  17.     QueryPerformanceFrequency(&f);  
  18.     dqFreq=(double)f.QuadPart;  
  19.       
  20.     QueryPerformanceCounter(&time_start);  
  21. }  
  22.   
  23. // unit: ms  
  24. static inline timing_t stopTiming()  
  25. {  
  26.     QueryPerformanceCounter(&time_over);  
  27.     return ((double)(time_over.QuadPart-time_start.QuadPart)/dqFreq*1000);  
  28. }  
  29.   
  30. static inline timing_t stopWithPrintTiming()  
  31. {  
  32.     timing_t timing;  
  33.     QueryPerformanceCounter(&time_over);  
  34.     timing = ((double)(time_over.QuadPart-time_start.QuadPart)/dqFreq*1000);  
  35.     printf("----------Elapsed Timing(ms) : %.3lf\n", timing);  
  36.     printf("----------------------------------------\n");  
  37.   
  38.     return timing;  
  39. }  
  40.   
  41. #else  
  42. #include <unistd.h>  
  43. typedef unsigned long long int64;  
  44. #define timing_t int64  
  45. #if defined(__i386__)  
  46. inline int64 GetCycleCount() {  
  47.     int64 result;  
  48.     __asm__ __volatile__ ("rdtsc" : "=A" (result));  
  49.     return result;  
  50. }  
  51. #elif defined(__x86_64__)  
  52. inline int64 GetCycleCount()   
  53. {  
  54.     int64 hi, lo;  
  55.     __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));  
  56.     return ( (int64)lo)|( ((int64)hi)<<32 );  
  57. }  
  58. #endif  
  59.   
  60. static int64 ticks_start, ticks_end;  
  61.   
  62. static inline void startTiming()  
  63. {  
  64.     ticks_start = GetCycleCount();  
  65. }  
  66.   
  67. // unit: cycles  
  68. static inline int64 stopTiming()  
  69. {  
  70.     ticks_end = GetCycleCount();  
  71.     return (ticks_end - ticks_start);  
  72. }  
  73.   
  74. static inline int64 stopWithPrintTiming()  
  75. {  
  76.     int64 timing;  
  77.     ticks_end = GetCycleCount();  
  78.     timing = (ticks_end - ticks_start);  
  79.     printf("----------Elapsed Timing(Cycles) : %llu\n", timing);  
  80.     printf("----------------------------------------\n");  
  81.   
  82.     return timing;  
  83. }  
  84. #endif  
  85.   
  86. // unit: ms  
  87. static inline void wait(int ms)  
  88. {  
  89. #ifdef WIN32  
  90.     Sleep(ms);  
  91. #else  
  92.     usleep(ms*1000);  
  93. #endif  
  94. }  
  95.   
  96. #endif  

  1. // foo.cpp  
  2. #include "timing.h"  
  3.   
  4. int main()  
  5. {  
  6.     timing_t ticks_start, ticks_end;  
  7.     startTiming();  
  8.   
  9.     wait(3000); // 3 seconds  
  10.     timing_t timing = stopWithPrintTiming();  
  11.       
  12.     return 1;  
  13. }  

Windows上编译:cl foo.cpp /DWIN,分别启动64bit和32bit的VS控制台进行测试。

Linux上编译:gcc foo.cpp -m32和gcc foo.cpp -m64进行测试。



C语言中常用计时方法总结

1. time()

头文件:time.h

函数原型:time_t time(time_t * timer)

功能:返回以格林尼治时间(GMT)为标准,从1970年1月1日00:00:00到现在的此时此刻所经过的秒数。

用time()函数结合其他函数(如:localtime、gmtime、asctime、ctime)可以获得当前系统时间或是标准时间。

用difftime函数可以计算两个time_t类型的时间的差值,可以用于计时。用difftime(t2,t1)要比t2-t1更准确,因为C标准中并没有规定time_t的单位一定是秒,而difftime会根据机器进行转换,更可靠。

用法

[cpp]  view plain  copy
 print ?
  1. time_t start,end;  
  2. start =time(NULL);//or time(&start);  
  3. //…calculating…  
  4. end =time(NULL);  
  5. printf("time=%d\n",difftime(end,start));  
总结:C标准库中的函数,可移植性最好,性能也很稳定,但精度太低,只能精确到秒,对于一般的事件计时还算够用,而对运算时间的计时就明显不够用了。

2. clock()

头文件:time.h

函数原型:clock_t clock(void);

功能:该函数返回值是硬件滴答数,要换算成秒,需要除以CLK_TCK或者 CLK_TCKCLOCKS_PER_SEC。比如,在VC++6.0下,这两个量的值都是1000。

用法

[cpp]  view plain  copy
 print ?
  1. clock_t start,end;  
  2. start = clock();  
  3. //…calculating…  
  4. end = clock();  
  5. printf("time=%f\n",(double)end-start)/CLK_TCK);  
总结:可以精确到毫秒,适合一般场合的使用。

3. timeGetTime()

WIN32API

头文件:Mmsystem.h  引用库: Winmm.lib

函数原型:DWORD timeGetTime(VOID);

功能:返回系统时间,以毫秒为单位。系统时间是从系统启动到调用函数时所经过的毫秒数。注意,这个值是32位的,会在0到2^32之间循环,约49.71天。

用法

[cpp]  view plain  copy
 print ?
  1. DWORDstart,end;  
  2. start= timeGetTime();  
  3. //…calculating…  
  4. end= timeGetTime();  
  5. printf("time=%d\n",end-start);  
总结:该函数的时间精度是五毫秒或更大一些,这取决于机器的性能。可用timeBeginPeriod和timeEndPeriod函数提高timeGetTime函数的精度。如果使用了,连续调用timeGetTime函数,一系列返回值的差异由timeBeginPeriod和timeEndPeriod决定。

4. GetTickCount()

WIN32API

头文件:windows.h

函数原型:DWORD WINAPI GetTickCount(void);

功能:返回自设备启动后的毫秒数(不含系统暂停时间)。

用法

[cpp]  view plain  copy
 print ?
  1. DWORDstart,end;  
  2. start= GetTickCount();  
  3. //…calculating…  
  4. end= GetTickCount();  
  5. printf("time=%d\n",end-start);  
总结:精确到毫秒。对于一般的实时控制,使用GetTickCount()函数就可以满足精度要求。

5. QueryPerformanceCounter()、QueryPerformanceFrequency()

WIN32API

头文件:windows.h

函数原型:BOOLQueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);

          BOOLQueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);

功能:前者获得的是CPU从开机以来执行的时钟周期数。后者用于获得你的机器一秒钟执行多少次,就是你的时钟周期。

补充:LARGE_INTEGER既可以是一个8字节长的整型数,也可以是两个4字节长的整型数的联合结构, 其具体用法根据编译器是否支持64位而定:

[cpp]  view plain  copy
 print ?
  1. typedef union_LARGE_INTEGER  
  2. {  
  3.     struct  
  4.     {  
  5.         DWORD LowPart ;  
  6.         LONG HighPart;  
  7.     };  
  8.     LONGLONG QuadPart ;  
  9. }LARGE_INTEGER;  

用法

在进行定时之前,先调用QueryPerformanceFrequency()函数获得机器内部定时器的时钟频率,然后在需要严格定时的事件发生之前和发生之后分别调用QueryPerformanceCounter()函数,利用两次获得的计数之差及时钟频率,计算出事件经历的精确时间。

[cpp]  view plain  copy
 print ?
  1. LARGE_INTEGER  num;  
  2. longlong start,end,freq;  
  3. QueryPerformanceFrequency(&num);  
  4. freq=num.QuadPart;  
  5. QueryPerformanceCounter(&num);   
  6. start= num.QuadPart;   
  7. //…calculating…  
  8. QueryPerformanceCounter(&num);   
  9. end= num.QuadPart;    
  10. printf("time=%d\n",(end-start)*1000/freq);  
总结:这种方法的定时误差不超过1微秒,精度与CPU等机器配置有关,一般认为精度为透微秒级。在Windows平台下进行高精度计时的时候可以考虑这种方法。

6. gettimeofday()

Linux C函数。

头文件:sys/time.h

函数原型:int gettimeofday(struct timeval *tv,struct timezone *tz);

说明:其参数tv是保存获取时间结果的结构体,参数tz用于保存时区结果(若不使用则传入NULL即可)。

timeval的定义为:

[cpp]  view plain  copy
 print ?
  1. struct timeval {  
  2.   long tv_sec; // 秒数  
  3.   long tv_usec; //微秒数  
  4. }  
可见该函数可用于在linux中获得微秒精度的时间。

用法

[cpp]  view plain  copy
 print ?
  1. struct timeval start,end;  
  2. gettimeofday(&start, NULL );  
  3. //…calculating…  
  4. gettimeofday(&end, NULL );  
  5. long timeuse =1000000 * ( end.tv_sec - start.tv_sec ) + end.tv_usec - start.tv_usec;  
  6. printf("time=%f\n",timeuse /1000000.0);  
总结:使用这种方式计时,精度可达微秒。经验证,在arm+linux的环境下此函数仍可使用。推荐。

7. RDTSC - 读取时间标签计数器

X86架构CPU汇编指令。

操作码:0F 31 指令:RDTSC

功能:将时间标签计数器读入 EDX:EAX寄存器中。

说明:在Pentium以上的CPU中,提供了一条机器指令RDTSC来读取这个时间戳的数字,并将其保存在EDX:EAX寄存器对中。由于EDX:EAX寄存器对恰好是Win32平台下C++语言保存函数返回值的寄存器,所以我们可以把这条指令看成是一个普通的函数调用:

[cpp]  view plain  copy
 print ?
  1. inline unsigned long longGetCycleCount()   
  2. {   
  3.     __asm RDTSC   
  4. }  
如果编译器不允许直接用RDTSC的话,可以用_emit伪指令直接嵌入该指令的机器码形式0X0F、0X31:

[cpp]  view plain  copy
 print ?
  1. inline unsigned long long GetCycleCount()   
  2. {  
  3.     __asm _emit 0x0F   
  4.     __asm _emit 0x31  
  5. }   
计算时还需要将得到的数字除以CPU的主频(单位GHZ),就能得到纳秒级的时间了。暂时我还没找到好的获得机器主频的方法,Windows平台下可以考虑用QueryPerformanceFrequency()函数,但这样一来就没办法在Linux下使用此方法。后来我考虑配合sleep函数,获取1秒中的机器周期数的方法来得到CPU主频。如果哪位有更好的方法,还请多多请教。

[cpp]  view plain  copy
 print ?
  1. #ifdef WIN32  
  2. #include <windows.h>  
  3. #else  
  4. #include <sys/unistd.h>  
  5. #endif  
  6. inline unsigned long long GetNTime()  
  7. {  
  8.     __asm("RDTSC");  
  9. }  
  10.   
  11. static double hz=0.0;  
  12.   
  13. void init_timer()  
  14. {  
  15.     longlong t1=GetNTime();  
  16. #ifdef WIN32  
  17.     Sleep(1000);  
  18. #else  
  19.     sleep(1);  
  20. #endif  
  21.     longlong t=GetNTime()-t1;  
  22.     hz=(double)t/1000000000;  
  23.     printf("hz=%fGhz\n",hz);  
  24. }  
  25.   
  26. long long u_timer(long long *t,int mode)  
  27. {  
  28.     if(hz<0.001)  
  29.         init_timer();  
  30.     if(!mode)  
  31.     {  
  32.         *t=GetNTime();  
  33.         return0;  
  34.     }  
  35.   
  36.     longlong t1=GetNTime()-*t;  
  37.     t1/=hz;  
  38.     longlong ns=t1%1000;  
  39.     longlong us=(t1/1000)%1000;  
  40.     longlong ms=(t1/1000000)%1000;  
  41.     longlong s=t1/1000000000;  
  42.   
  43.     printf("time=");  
  44.     if(s!=0)  
  45.         printf("%llds",s);  
  46.     if(ms!=0)  
  47.         printf("%lldms",ms);  
  48.     if(us!=0)  
  49.         printf("%lldus",us);  
  50.     if(ns!=0)  
  51.         printf("%lldns",ns);  
  52.     printf("\n");  
  53.   
  54.     *t=GetNTime();  
  55.     returnt1;  
  56. }  
总结:这种方法精确到纳秒,但缺点是非常短时间的计时会不稳定。最近因为项目需要,我也找了一下ARM+Linux平台上可以用的计时方法,后来选择了gettimeofday()。不知道ARM+Linux平台上有没有类似RDTSC的这种指令。




Logo

更多推荐