Linux用户态程序计时方式详解(1)(3)
2.3 gettimeofday函数
gettimeofday是个库函数,函数原型声明在sys/time.h头文件中:
int gettimeofday(struct timeval *tv,struct timezone *tz); |
该函数查询系统时钟,并将当前时间存入tv所指结构体,当地时区信息存入tz所指结构体。其结构体定义为:
1 struct timeval{ 2 time_t tv_sec; //当前时间距UNIX时间基准的秒数 3 suseconds_t tv_usec; //一秒之内的微秒数,且1000000>tv_usec>=0 4 }; 5 struct timezone{ 6 int tz_minuteswest; //和Greenwich时间相差多少分钟 7 int tz_dsttime; //日光节约时间的状态 8 };
tv或tz均可为空,为空时不返回对应的结构体。通常只会获取当前时间,故置时区指针tz为空。
相对于间隔计数的小适用范围和周期计数的麻烦性,gettimeofday是一个可移植性更好相对较准确的方法。在Linux系统中,该函数计时精度可达到微秒级。
测量
某程序执行时间时,可在待计时程序段起始和结束处分别调用gettimeofday函数,用后一次获取的当前时间减去前一次获取的当前时间得到运行该程序所消耗的秒或微秒数。如:
1 #include <sys/time.h> 2 #define TIME_ELAPSED(codeToTime) do{ \ 3 struct timeval beginTime, endTime; \ 4 gettimeofday(&beginTime, NULL); \ 5 {codeToTime;} \ 6 gettimeofday(&endTime, NULL); \ 7 long secTime = endTime.tv_sec - beginTime.tv_sec; \ 8 long usecTime = endTime.tv_usec - beginTime.tv_usec; \ 9 printf("[%s(%d)]Elapsed Time: SecTime = %lds, UsecTime = %ldus!\n", __FUNCTION__, __LINE__, secTime, usecTime); \ 10 }while(0) 11 12 void GetTimeofDayTiming(void){ 13 struct timeval tBeginTime, tEndTime; 14 gettimeofday(&tBeginTime, NULL); 15 TimingFunc(); 16 gettimeofday(&tEndTime, NULL); 17 float fCostTime = 1000000*(tEndTime.tv_sec-tBeginTime.tv_sec)+ //先减后加避免溢出! 18 (tEndTime.tv_usec-tBeginTime.tv_usec); 19 fCostTime /= 1000000; 20 printf("[gettimeofday]Cost Time = %fSec\n", fCostTime); 21 }
使用gettimeofday函数计时时应注意:
1) 该函数的实现因系统和平台而异,故计时精度也随之而异。Linux系统直接提取硬件时钟来实现该函数,故精度接近周期计数精度;而Windows NT系统使用间隔计数实现,故精度较低。i386平台下采用内核sys_gettimeofday系统调用实现,调用时会向内核发送软中断,然后陷入内核态,内核进行软中断等处理并将执行结果复制到用户态,这些成本超过1微秒;而x86_64平台下采用vsyscall虚拟系统调用实现,创建一个用户态有权限访问的内核态共享内存页面,不通过中断即可获取系统时间,调用成本不到1微秒。
2) 该函数依赖于系统时间,若系统时间被人为改变则获取的时间随之改变。
3) 若计时过程中系统正在运行其他后台程序,可能会影响到最终的计时结果。
可用gettimeofday函数和usleep调用精确地计算处理器主频,如下:
1 void CalcCpuFreq3(void){ 2 struct timeval tStartTime, tEndTime; 3 4 cycle_t tStart = CurrentCycle(); 5 gettimeofday(&tStartTime, NULL); 6 usleep(1000000); //精度不高,由gettimeofday加以补偿 7 cycle_t tEnd = CurrentCycle(); 8 gettimeofday(&tEndTime, NULL); 9 10 int dwUsecDelay = 1000000 * (tEndTime.tv_sec - tStartTime.tv_sec) + 11 (tEndTime.tv_usec - tStartTime.tv_usec); 12 printf("CPU Frequency: %lldMHz\n", (tEnd-tStart)/dwUsecDelay); 13 }
2.4 clock函数
clock是ANSI C标准库函数,其函数原型声明在time.h头文件中:
clock_t clock(void); |
该函数返回自待测试程序进程开始运行起,到程序中调用clock函数时的处理器时钟计时单元数(俗称clock tick,即硬件时钟滴答次数)。若无法得到处理器时间,则返回-1。时钟计时单元的长短由CPU控制,但clock tick并非CPU时钟周期,而是一个C/C++基本计时单位。返回类型clock_t通常定义为有符号长整型(long int)。
使用clock函数时应注意以下几点:
1) 该函数返回处理器耗费在某程序上的时间(CPU时间片数量)。若程序中存在sleep函数,则sleep所消耗的时间将不计算在内,因为此时CPU资源被释放。
2) 返回值若以秒计需除以CLOCKS_PER_SEC宏,该宏表示一秒钟有多少个时钟计时单元(硬件滴答数),取值因系统而异。在POSIX兼容系统中,CLOCKS_PER_SEC值为1,000,000,即1MHz(此时返回值单位为微秒)。通过(231-1)/1000000/60≈35.8可估算出clock函数超过半小时后将会溢出。
3) 该函数仅能返回毫秒级的计时精度(大致与操作系统的线程切换时间相当),低于精度的程序计为0毫秒。因此,该函数适用于测量一些耗时较长(大于10ms)的大型程序或循环程序。
4) 当程序单线程或单核心机器运行时,该函数计时准确;但多线程环境下并发执行时不可使用,因为结束时间与起始时间之差是多个核心总共执行的时钟滴答数,会造成计时偏大。
5) 该函数未考虑CPU被子进程使用的情况,也不能区分用户模式和内核模式。该函数计量进程占用的CPU时间,大约是用户时间和系统时间的总和。
测量
某程序执行时间时,可在待计时程序段起始和结束处分别调用clock函数,用后一次的返回值减去前一次的返回值得到运行该程序所消耗的处理器时钟计时单元数,再除以CLOCKS_PER_SEC转换为秒。如:
1 #include <time.h> 2 void ClockTiming(void){ //可尝试在计时间隔内调用sleep(5),观察计时结果是否增加5秒 3 clock_t tBeginTime = clock(); //记录起始时间 4 TimingFunc(); //待计时函数 5 clock_t tEndTime = clock(); //记录结束时间 6 double fCostTime = (double)(tEndTime - tBeginTime)/CLOCKS_PER_SEC; //注意类型强制转换 7 printf("[clock]Cost Time = %fSec\n", fCostTime); 8 }
2.5 time函数
time是ANSI C标准库函数,其函数原型声明在time.h头文件中:
time_t time(time_t * timer); |
该函数返回当前的日历时间(以秒计)。若参数timer为非NULL指针,则时间值也通过该指针存储。若机器无法提供当前时间,或时间值过大而无法用time_t表示,则函数返回(time_t)-1。返回类型time_t通常定义为有符号长整型(long)。
测量
某程序执行时间时,可在待计时程序段起始和结束处分别调用time函数,用后一次的返回值减去前一次的返回值即可得到运行该程序所消耗的秒数。如:
1 #include <time.h> 2 void TimeTiming(void){ 3 time_t tBeginTime = time(NULL); 4 TimingFunc(); 5 time_t tEndTime = time(NULL); 6 double fCostTime = difftime(tEndTime, tBeginTime); 7 printf("[time]Cost Time = %fSec\n", fCostTime); 8 }
注意,时间类型time_t是个“可表示时间的算术类型(arithmetic type capable of representing times)”别名。但C标准并未规定time函数中该算术类型的时间编码方式。POSIX规定time函数必须返回一个时间整数,表示自Epoch(00:00 hours, Jan 1, 1970 UTC)以来的秒数;但库函数可能采用不同的时间表示方式。因此不应使用字面值常量,因其含义可能因编译器而异。
遵循POSIX规范的程序可直接对time_t对象进行算术运算;可移植程序则应调用相关标准库函数(如localtime、gmtime或difftime),将time_t对象转换为可移植类型。TimeTiming函数即使用difftime函数将先后调用time所获得的时间差值转换为秒。
Linux下time返回值为秒数,故difftime调用处等效于double fCostTime = (double)(tEndTime-tBeginTime)。注意,虽然difftime函数返回类型为double类型,但其值为以秒计的时间间隔,故只能精确到秒。
以下代码分别给出两种版本,以实现在至少dwWorkSec(秒)时间内多次执行TimingFunc:
1 #include <time.h> 2 int NoncompliantWork(int dwWorkSec){ 3 time_t tStart = time(NULL); 4 if(tStart == (time_t)(-1)) 5 return -1; 6 7 while(time(NULL) < tStart + dwWorkSec){ //时间编码方式未定义,故加法运算不能保证增加dwWorkSec秒 8 TimingFunc(); //Do some work 9 } 10 return 0; 11 } 12 int CompliantWork(int dwWorkSec){ 13 time_t tStart = time(NULL); 14 time_t tCurrent = tStart; 15 if(tStart == (time_t)(-1)) 16 return -1; 17 18 while(difftime(tCurrent, tStart) < dwWorkSec){ //因time_t表示范围所限,可能造成死循环(infinite loop) 19 TimingFunc(); //Do some work 20 tCurrent = time(NULL); 21 if(tCurrent == (time_t)(-1)) 22 return -1; 23 } 24 return 0; 25 }
2.6 clock_gettime函数
clock_gettime是POSIX1003.1实时函数,其函数原型声明在time.h头文件中:
int clock_gettime(clockid_t clk_id, struct timespec *tp); |
该函数获取tp关于指定时钟的当前timespec值,并将其存入指针tp所指结构体。其结构体定义为:
1 struct timespec{ 2 time_t tv_sec; //自1970年7月1日以来经过的秒数 3 long tv_nsec; //自上一秒开始经过的纳秒数(nanoseconds) 4 }
可见,该函数计时精度达到纳秒级。若函数执行成功,则返回0;否则返回一个错误码。
clockid_t值用于指定计时器的类型,POSIX.1b所支持的标准计时器如下:
- CLOCK_REALTIME:系统范围内的实时时钟,反映挂钟时间(wall clock time),即绝对时间。若系统时钟源被改变或系统时间被重置,该时钟会相应地调整。若指定该时钟类型,clock_gettime函数等效于gettimeofday函数,尽管精度有所不同。
- CLOCK_MONOTONIC:单调时间,不可设置。该时间通过jiffies值计算,其值为当前时间减去起始时间之差,即从系统启动至今所经过的时间。单调时间在运行期间会一直稳定增加,而不受系统时钟的影响。若指定该时钟类型,则tv_sec值与“cat /proc/uptime”第一个输出值(秒)相同。
- CLOCK_PROCESS_CPUTIME_ID:每个进程的CPU高精度硬件计时器。
- CLOCK_THREAD_CPUTIME_ID:每个线程的CPU高精度硬件计时器。
因为CLOCK_MONOTONIC
计时器更加稳定,故推荐以此获得系统的运行时间。结合/proc/uptime
文件,可通过以下几种方式获得
系统自举以来的秒数:
1 #include <fcntl.h> 2 #include <unistd.h> 3 //通过文件接口读取/proc/uptime中的值进行字符串的转换 4 int GetSysTime(int *pSec, int *pMsec){ 5 if(NULL == pSec && NULL == pMsec) 6 return -1; 7 8 int dwFd = open("/proc/uptime", O_RDONLY); 9 if(dwFd <= 0) 10 return -2; 11 12 char acReadBuf[128] = {0}; 13 if(read(dwFd, acReadBuf, sizeof(acReadBuf)) <= 0) 14 return -3; 15 16 int dwSecond = 0, dwMsecond = 0; 17 sscanf(acReadBuf, "%d.%d[^ ]", &dwSecond, &dwMsecond); 18 if(pSec != NULL) 19 *pSec = dwSecond; 20 if(pMsec != NULL) 21 *pMsec = dwMsecond; 22 23 close(dwFd); 24 return 0; 25 } 26 27 #include <sys/syscall.h> 28 //利用__NR_clock_gettime系统调用直接获取(编译链接时无需-lrt选项) 29 int GetSysTime2(int *pSec, int *pMsec){ 30 if(NULL == pSec && NULL == pMsec) 31 return -1; 32 33 struct timespec tSpec; 34 memset(&tSpec, 0, sizeof(tSpec)); 35 syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &tSpec); 36 37 if(pSec != NULL) 38 *pSec = tSpec.tv_sec; 39 if(pMsec != NULL) 40 *pMsec = tSpec.tv_nsec/1000; 41 42 return 0; 43 } 44 45 int GetSysTime3(int *pSec, int *pMsec){ 46 if(NULL == pSec && NULL == pMsec) 47 return -1; 48 49 struct timespec tSpec; 50 memset(&tSpec, 0, sizeof(tSpec)); 51 clock_gettime(CLOCK_MONOTONIC, &tSpec); 52 53 if(pSec != NULL) 54 *pSec = tSpec.tv_sec; 55 if(pMsec != NULL) 56 *pMsec = tSpec.tv_nsec/1000; 57 58 return 0; 59 }
注意,/proc/uptime
文件
第二列输出为系统空闲的时间(以秒为单位),该时间计算时会计入SMP系统中所有逻辑CPU。
测量某程序执行时间时,可在待计时程序段起始和结束处分别调用clock_gettime函数,用后一次获取的当前时间减去前一次获取的当前时间得到运行该程序所消耗的秒或微秒数。如:
1 #include <time.h> 2 void ClockGetTimeTiming(void){ 3 struct timespec tBeginTime, tEndTime; 4 clock_gettime(CLOCK_MONOTONIC, &tBeginTime); 5 TimingFunc(); 6 clock_gettime(CLOCK_MONOTONIC, &tEndTime); 7 double fCostTime = (tEndTime.tv_sec-tBeginTime.tv_sec) + 8 (double)(tEndTime.tv_nsec-tBeginTime.tv_nsec)/1000000000; 9 printf("[clock_gettime]Cost Time = %fSec\n", fCostTime); 10 }
注意,编译链接时需加上-lrt选项,因为clock_gettime函数在librt库中实现。
以下代码通过settimeofday函数将当前系统时间往回设置10秒,对比gettimeofday和clock_gettime所受的影响。注意,只有root权限才能调用settimeofday函数修改当前时间。
1 #include <time.h> 2 #include <unistd.h> 3 #include <sys/time.h> 4 5 void ChangeSysTime(void){ 6 struct timeval tv1, tv2; 7 struct timespec ts1, ts2; 8 9 gettimeofday(&tv1, NULL); 10 clock_gettime(CLOCK_MONOTONIC, &ts1); 11 12 struct timeval temp = tv1; 13 temp.tv_sec -= 10; 14 settimeofday(&temp, NULL); //将当前系统时间往回设置10秒 15 gettimeofday(&tv2, NULL); 16 clock_gettime(CLOCK_MONOTONIC, &ts2); 17 18 printf("gettimeofday: [%ld.%6ld ~ %ld.%6ld] => diff = %f\n", tv1.tv_sec, tv1.tv_usec, tv2.tv_sec, tv2.tv_usec, 19 ((tv2.tv_sec*1000000+tv2.tv_usec)-(tv1.tv_sec*1000000+tv1.tv_usec))/1000000.0); 20 printf("clock_gettime: [%ld.%9ld ~ %ld.%9ld] => diff = %f\n", ts1.tv_sec, ts1.tv_nsec, ts2.tv_sec, ts2.tv_nsec, 21 ((ts2.tv_sec*1000000000+ts2.tv_nsec)-(ts1.tv_sec*1000000000+ts1.tv_nsec))/1000000000.0); 22 23 tv2.tv_sec += 10; 24 settimeofday(&tv2, NULL); //恢复系统时间 25 gettimeofday(&tv2, NULL); 26 printf("gettimeofday2: [%ld.%6ld]\n", tv2.tv_sec, tv2.tv_usec); 27 }
执行结果输出如下:
可见,当系统时间被人为改动时,gettimeofday函数计算的时间差存在偏差;clock_getime函数计时则不受影响,仅与实际所经历的时间相关。
评论暂时关闭