1、 测试方法分类
1.1概述
1.1.1 常见方法
目前查到的方法有:
- time()
- clock()
- rdtsc
- QueryPerformanceFrequency()
- timeGetTime()
- GetTickCount()
- gettimeofday
方法 | 类型 | 总结 | |
---|---|---|---|
1 | time() | time.h | C标准库,可移植性好、性能稳定,精度低(S) |
2 | clock() | time.h | 计算的是耗用多少个CPU的时钟单元,精确毫秒 |
3 | timeGetTime() | WIN32API | 精度五毫秒或更大一些,这取决于机器的性能 |
4 | GetTickCount() | WIN32API | 满足一般的实时控制,精确毫秒 |
5 | gettimeofday() | Linux C函数 | 在arm+linux的环境下此函数仍可使用,精度可达微秒 |
6 | RDTSC - 读取时间标签计数器 | X86架构CPU汇编指令 | 缺点是非常短时间的计时会不稳定,精度ns |
7 | QueryPerformanceFrequency() | Win32API | 高精度定时,精度微妙 |
考虑到移植性与精度,使用gettimeofday()
clock函数计算的是耗用多少个CPU的时钟单元,使用sleep()并不占用cpu资源,所以clock不会计算在内。一般只可以用于单线程的情况,否则使用clock函数得到的时间偏大。time 与 gettimeofday两个函数得到的都是从Epoch开始到当前的秒数(tt=tv.tv_sec),而gettimeofday 还能得到更精细的微秒级结果,即tv_sec*(10^6)+tv_usec为从Epoch开始到当前的微秒数。time 和gettimeofday返回绝对时间,不受程序执行的影响,clock返回的是相对时间,是程序执行的时间,程序执行时间受到进程调度、阻塞等影响,而绝对时间不会。所以,当需要统计包含子程序在内的时间时,还是需要以绝对时间相减的形式计算,而不能使用clock简单统计,clock只能统计父进程的执行时间。[14][15][16][17]。
1.1.2 time.h
1) 概念
1:Coordinated Universal Time(UTC):
协调世界时,又称世界标准时间,也即格林威治标准时间(Greenwich Mean Time,GMT),中国内地的时间与UTC得时差为+8,也即UTC+8,美国为UTC-5。
2:Calendar Time:
日历时间,是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。标准时间点对不同编译器可能会不同,但对一个编译系统来说,标准时间是不变的。一般是表示距离UTC时间 1970-01-01 00:00:00的秒数。
3:epoch:
时间点。在标准c/c++中是一个整数,用此时的时间和标准时间点相差的秒数(即日历时间)来表示。
4:clock tick:
时钟计时单元(而不叫做时钟滴答次数),一个时钟计时单元的时间长短是由cpu控制的,一个clock tick不是cpu的一个时钟周期,而是c/c++的一个基本计时单位。
Sleep函数:
功 能: 执行挂起一段时间
用 法: unsigned sleep(unsigned seconds);
注意:
在VC中使用带上头文件#include <windows.h>,在Linux下,gcc编译器中,使用的头文件因gcc版本的不同而不同#include <unistd.h>
在VC中,Sleep中的第一个英文字符为大写的"S" ,在linux下不要大写,在标准C中是sleep, 不要大写,简单的说VC用Sleep, 别的一律使用sleep
在VC中,Sleep()里面的单位,是以毫秒为单位,所以如果想让函数滞留1秒的话,应该是Sleep(1000); 在Linux下,sleep()里面的单位是秒,而不是毫秒。
2) 变量解释
time.h 头文件定义了四个变量类型、两个宏和各种操作日期和时间的函数[1]。
4个变量
类型 | 描述 |
---|---|
size_t | 是无符号整数类型,它是 sizeof 关键字的结果。 |
clock_t | 这是一个适合存储处理器时间的类型,类型为unsigned long |
time_t | 这是一个适合存储日历时间类型。 |
struct tm | 这是一个用来保存时间和日期的结构。 |
tm 结构的定义如下:
struct tm
{
int tm_sec; /* 秒,范围从 0 到 59 */
int tm_min; /* 分,范围从 0 到 59 */
int tm_hour; /* 小时,范围从 0 到 23 */
int tm_mday; /* 一月中的第几天,范围从 1 到 31 */
int tm_mon; /* 月,范围从 0 到 11(注意) */
int tm_year; /* 自 1900 年起的年数 */
int tm_wday; /* 一周中的第几天,范围从 0 到 6 */
int tm_yday; /* 一年中的第几天,范围从 0 到 365 */
int tm_isdst; /* 夏令时 */
};
两个宏
类型 | 描述 |
---|---|
NULL | 这个宏是一个空指针常量的值。 |
CLOCKS_PER_SEC | 这个宏表示每秒的处理器时钟个数。用于将clock()函数的结果转化为以秒为单位的量,这个量的具体值是与操作系统相关的,通常为1000。 |
1.2 具体实现
1.2.1 time()
精度:秒
/*
函数原型: time_t time(time_t *timer)
参数说明: timer=NULL时得到当前日历时间(从1970-01-01 00:00:00
到现在的秒数),timer=时间数值时,用于设置日历时间,
time_t是一个unsigned long类型。如果 timer不为空,
则返回值也存储在变量 timer中。
函数功能: 得到当前日历时间或者设置日历时间
函数返回: 当前日历时间
*/
#include <stdio.h>
#include <time.h>
int main(){
time_t start,end;
start = time(NULL);
/*...
需要计时的代码
...*/
end = time(NULL);
printf("time = %ds\n", difftime(end, start));
return 0;
}
参考[1][2][3]
1.2.2 clock()
函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数。clock_t其实就是long,即长整形。该函数返回值是硬件滴答数,要换算成秒或者毫秒,需要除以CLK_TCK或者CLOCKS_PER_SEC。注意:本函数仅能返回ms级的计时精度(事实上能够达到的计时精度大致与操作系统的线程切换时间相当,在windows平台上,极限精度大致是15~16ms)。如果需要us级别的计时精度,Linux系统可以使用库函数:gettimeofday()[4][9]。
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(){
clock_t start, end;
start = clock();
/*...
需要计时的代码
...*/
end = clock();
printf("time=%f\n", (double)(end - start) / CLOCKS_PER_SEC);
return 0;
}
注意:
CLOCKS_PER_SEC的值不是每秒的实际时钟滴答数,clock()是指挂钟而不是CPU时钟,准确度通常会略低[5]。
1.2.3 timeGetTime()
函数以毫秒计的系统时间。该时间为从系统开启算起所经过的时间。
1.2.4 GetTickCount()
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include <string.h>
#include<math.h>
#include <time.h>
#include <pthread.h>
#include <windows.h>//使用Sleep的头
int main(void)
{
double t1 = (double)GetTickCount();
/******************待测代码********************/
Sleep(1000);
/**********************************************/
double t2 = (double)GetTickCount();
// printf("\ntime in %lf ms\n",(t2-t1)*1000/(cvGetTickFrequency()));
// printf("\ntime in %lf ms\n",(t2-t1));
printf("%lf,%lf\n",t1,t2);
printf("\ntime in %lf ms\n",(t2-t1));
return 0;
}
代码参考:
1.2.5 gettimeofday
#include <time.h>
#include <stdio.h>
#include<sys/time.h>
#define NEW_TIME_VALE
struct timeval startTime,endTime; \
float Timeuse;\
#define START_GETTIME \
gettimeofday(&startTime,NULL); \
#define END_GETTIME \
gettimeofday(&endTime,NULL); \
Timeuse = 1000000*(endTime.tv_sec - startTime.tv_sec) + (endTime.tv_usec - startTime.tv_usec); \
Timeuse /= 1000000; \
printf("Timeuse = %f\n",Timeuse);\
int main(void)
{
NEW_TIME_VALE
START_GETTIME
/******************待测时间代码*********************/
Sleep(1000);
/**************************************************/
END_GETTIME
return 0;
}
代码参考:[11]
1.2.6 rdtsc
Linux提供的API—gettimeofday()可以获取微秒级的精度。但是,首先它不能提供纳秒级精度,其次,他是一个库函数(可能不是系统调用),自身就有一定的开销,当我需要纳秒级精度时,误差会很大。而且,测定函数的性能以时钟周期为单位比纳秒更加合理。当不同型号的CPU的频率不同时,运行时间可能差很多,但是时钟周期应该差不了多少(如果指令集一样的话)。
x86处理器为我们提供了rdtsc指令。从pentium开始,很多x86处理器都引入了TSC(Time Stamp Counter),一个64位的寄存器,每个CPU时钟周期其值加一。它记录了CPU从上电开始总共经历的时钟周期数。一个2.5GHz的CPU如果全速运行的话,那么其TSC就会在一秒内增加2,500,000,000(1GHz = 10^9 Hz)。使用rdtsc获得时间会比gettimeofday的方式开销小。
rdtsc指令把TSC的低32位存放在EAX寄存器中,把TSC的高32位存放在EDX寄存器中。该指令可以在用户态执行。可以使用GCC内嵌汇编实现在用户态获取时钟周期[6]。
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include <string.h>
#include<math.h>
#include <time.h>
#include <pthread.h>
#include <windows.h>//使用Sleep的头
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long int uint64_t;
uint64_t current_cycles(void)
{
uint32_t low, high;
asm volatile("rdtsc" : "=a"(low), "=d"(high));
return ((uint64_t)low) | ((uint64_t)high << 32);
}
uint64_t current_system_frequency(void)
{
uint64_t tick,tick1,time;
tick = current_cycles();
Sleep(1000);
tick1 = current_cycles();
time = (unsigned)((tick1-tick));
return time;
}
int main(void)
{
uint64_t sys_fre=0;
uint64_t tick,tick1;
double time;
sys_fre = current_system_frequency(); //获得系统主频
tick = current_cycles();
/******************待测代码********************/
Sleep(1000);
/**********************************************/
tick1 = current_cycles();
time =((double)(tick1-tick)/sys_fre);
printf("\ntime in %lf\n",time);
return 0;
}
但是RDTSC 指令测试指令周期和时间也存在一些问题[7]。
1. pu的是频率是可以变化的,频率变化之后,tick是不准确的,要怎么办?
2. 不同核心的tick是同步的么,如果不同步怎么办?
3. cpu执行的指令有可能是乱序的,可能是测量的不准确
对于问题1,2,可以查看cpu是否支持const tsc。支持这个特性的cpu,它的tsc是按照其标称频率流逝的,与CPU的实际工作频率与状态无关。
对于问题3,除了 TSC 要准,还要顾及 CPU 的乱序执行,尤其是如果被测的代码段较小,乱序执行可能让你的测试变得完全没意义。解决这个一般是“先同步,后计时”,在 wikipedia 上的一段代码就用 cpuid 指令来同步 (http://en.wikipedia.org/wiki/Time_Stamp_Counter
)。
1.2.7 QueryPerformanceFrequency()
在Windows平台下,常用的计时器有两种,一种是timeGetTime多媒体计时器,它可以提供毫秒级的计时。但这个精度对很多应用场合而言还是太粗糙了。另一种就是QueryPerformanceCount计数器,随系统的不同可以提供微秒级的计数。
在多核心或多处理器的计算机上,特别是在支持CPU频率动态调整的计算机上,windows系统下的QueryPerformanceFrequency()获取HPET (如果存在)的频率,而QueryPerformanceCounter() 获取HPET(如果存在高精度事件定时器(High Precision Event Timer))自上电以来时钟周期数,与CPU频率无关 。前一个函数 不会因为线程运行所在的CPU不同或CPU的频率在不同时刻的差异而反馈不同的值,系统上电初始化以后便不会改变 ,后一个函数的源也是统一的。这样便可以精确计算目标程序,特别是多线程程序的性能。
如果想获得CPU的默认信息,包括制造商,版本号,默认频率等等,请使用:__cpuid()。如果想获得当前CPU的实际频率,请获取系统当前基频和倍频然后计算得到。
注:但是这和程序具体的运行环境有关,程序运行的流畅顺利就所用的时间就短,程序运行的环境不好,持续时间久很长。虽然可以实现高精度定时计数,但是程序的执行时间还是和运行环境有关,无法做到精确测量[10]。
#include<stdio.h>
#include <stdlib.h>
#include<time.h>
#include <windows.h>
int main() {
double run_time;
LARGE_INTEGER time_start; //开始时间
LARGE_INTEGER time_over; //结束时间
double dqFreq; //计时器频率
LARGE_INTEGER f; //计时器频率
QueryPerformanceFrequency(&f);
dqFreq=(double)f.QuadPart;
QueryPerformanceCounter(&time_start); //计时开始
/******************待测时间代码*********************/
Sleep(1000);
/**************************************************/
QueryPerformanceCounter(&time_over); //计时结束
run_time=1000000*(time_over.QuadPart-time_start.QuadPart)/dqFreq;
//乘以1000000把单位由秒化为微秒,精度为1000 000/(cpu主频)微秒
printf("\nrun_time:%fus\n",run_time);
printf("\nrun_time:%fms\n",run_time/1000);
printf("\nrun_time:%fs\n",run_time/1000/1000);
return 0;
}
代码参考:[8]
参考链接
[1] C语言中的time函数总结:https://blog.youkuaiyun.com/hmxz2nn/article/details/77986450
[2] C 库函数 - time():https://www.runoob.com/cprogramming/c-function-time.html
[3] C语言time()函数的用法: https://blog.youkuaiyun.com/jaylee123123/article/details/80986913
[4] clock()函数的返回值精度问题: https://blog.youkuaiyun.com/Fire_to_cheat_/article/details/78265264
[5] clock()精度: https://www.it1352.com/1999135.html
[6] 使用rdtsc测量函数的执行时间: https://www.jianshu.com/p/0c519fc6248b
[7] 多核时代不宜再用 x86 的 RDTSC 指令测试指令周期和时间: https://blog.youkuaiyun.com/Solstice/article/details/5196544
[8] C语言 计算程序运行时间(精确到毫秒/微秒):https://blog.youkuaiyun.com/Bamboo_shui/article/details/78757281
[9] C语言程序执行时间计时方法汇总:https://www.cnblogs.com/-yhwu/p/14970736.html
[10] QueryPerformanceFrequency用法–Windows高精度定时计数: https://blog.youkuaiyun.com/duiwangxiaomi/article/details/103895384
[11] 使用gettimeofday测试函数运行的时间: https://www.cnblogs.com/mylinux/p/5295959.html
[12] 【c++】函数time和clock的计时区别: https://blog.youkuaiyun.com/qq525003138/article/details/107095324/
[13] time()函数和clock()函数的区别: https://blog.youkuaiyun.com/weixin_44218238/article/details/124122738
[14] C/C++获取时间方法:gettimeofday():https://blog.youkuaiyun.com/wu694128/article/details/94542858
[15] 函数time()与gettimeofday()的区别: https://blog.youkuaiyun.com/shanzhizi/article/details/7708820
[16] Linux 时间函数 (time() / clock() / gettimeofday()) 简介与比较: https://www.youkuaiyun.com/tags/MtjakgwsMzEzODktYmxvZwO0O0OO0O0O.html
[17] C语言中常用计时方法总结: https://www.bbsmax.com/A/xl56KrR1dr/