一、 Linux的time命令
Linux系统下统计程序运行实践最简单直接的方法就是使用time命令,文献[1, 2]中详细介绍了time命令的用法。此命令的用途在于测量特定指令执行时所需消耗的时间及系统资源等资讯,在统计的时间结果中包含以下数据:
(1) 实际时间(real time):从命令行执行到运行终止的消逝时间;
(2) 用户CPU时间(user CPU time):命令执行完成花费的系统CPU时间,即命令在用户态中执行时间的总和;
(3) 系统CPU时间(system CPU time):命令执行完成花费的系统CPU时间,即命令在核心态中执行时间的总和。
其中,用户CPU时间和系统CPU时间之和为CPU时间,即命令占用CPU执行的时间总和。实际时间要大于CPU时间,因为Linux是多任务操作系统,往往在执行一条命令时,系统还要处理其他任务。另一个需要注意的问题是即使每次执行相同的命令,所花费的时间也不一定相同,因为其花费的时间与系统运行相关。
二、间隔计数[4]
上面介绍的time命令能测量特定进程执行时所消耗的时间,它是怎么做到的呢?
操作系统用计时器来记录每个进程使用的累计时间,原理很简单,计时器中断发生时,操作系统会在当前进程列表中寻找哪个进程是活动的,一旦发现进程A正在运行立马就给进程A的计数值增加计时器的时间间隔(这也是引起较大误差的原因)。当然不是统一增加的,还要确定这个进程是在用户空间活动还是在内核空间活动,如果是用户模式,就增加用户时间,如果是内核模式,就增加系统时间。这种方法的原理虽然简单但不精确。如果一个进程的运行时间很短,短到和系统的计时器间隔一个数量级,用这种方法测出来的结果必然是不够精确的,头尾都有误差。不过,如果程序的时间足够长,这种误差有时能够相互弥补,一些被高估一些被低估,平均下来刚好。从理论上很难分析这个误差的值,所以一般只有程序达到秒的数量级时用这种方法测试程序时间才有意义。
这种方法最大的优点是它的准确性不是非常依赖于系统负载。
实现方法之一就是上面介绍的time命令,之二是使用tms结构体和times函数。
在Linux中,提供了一个times函数,原型是
clock_t times( struct tms * buf );
这个tms的结构体为
struct tms
{
clock_t tms_utime; //user time
clock_t tms_stime; //system time
clock_t tms_cutime; //user time of reaped children
clock_t tms_cstime; //system time of reaped children
}
这里的cutime和cstime,都是对已经终止并回收的时间的累计,也就是说,times不能监视任何正在进行中的子进程所使用的时间。使用times函数需要包含头文件sys/times.h。
三、周期计数[4]
四、gettimeofday 函数计时[4]
gettimeofday 是一个库函数,包含在time.h中。它的功能是查询系统时钟,以确定当前的日期和时间。相对于间隔计数的小适用范围和周期计数的麻烦性,gettimeofday是一个可移植性更好相对较准确的方法。它的原型如下:
struct timeval
{
long tv_sec; //秒域 seconds since the Epoch
long tv_usec; //微妙域
}
int gettimeofday( struct timeval *tv, NULL);
这个机制呢,具体的实现方式在不同系统上是不一样的,而且具体的精确程度是和系统相关的:比如在Linux下,是用周期计数来实现这个函数的,所以和周期计数的精确度差不多,但是在Windows NT下,是使用间隔计数实现的,精确度就很低了。
具体使用,就是在要计算运行时间的程序段之前和之后分别加上
gettimeofday( &tvstart, NULL)
…
gettimeofday( &tvend, NULL)
(tvend.tv_sec-tvstart.tv_sec)+(tvend.tv_usec-tvstart.tv_usec)/1000000
就得到了以秒为单位的计时时间。
五、clock 函数
clock 也是一个库函数,仍然包含在time.h中,函数原型是:
clock_t clock( void );
功能:返回自程序开始运行的处理器时间,如果无可用信息,返回 -1。转换返回值若以秒计需除以 CLOCKS_PER_SECOND。(注:如果编译器是POSIX兼容的,CLOCKS_PER_SECOND定义为 1000000。)[5]
使用clock函数也比较简单:在要计时程序段前后分别调用clock函数,用后一次的返回值减去前一次的返回值就得到运行的处理器时间,然后再转换为秒。举例如下:
clock_t starttime, endtime;
double totaltime;
starttime = clock();
…
endtime = clock();
totaltime = (double)( (endtime - starttime)/(double)CLOCKS_PER_SEC );
六、 time函数
在time.h中还包含另一个时间函数:time。文献[6]对其进行了详细的介绍。通过time()函数来获得日历时间(Calendar Time),其原型为:time_t time( time_t * timer )。通过difftime函数可以计算前后两次的时间差:double difftime( time_t time1, time_t time0 )。用time_t表示的时间(日历时间)是从一个时间点(例如:1970年1月1日0时0分0秒)到此时的秒数,则此函数的前后两次时间差也是以秒为单位。
比如:
time_t startT, endT;
double totalT;
startT = time( NULL );
…
endT = time( NULL );
totalT = difftime( startT, endT);
关于此函数的其他应用请参见文献[6]。
总结:
使用相应的方法,调用相应的函数,还需要关注它们可以表示的范围和精度,这样才能“挑肥拣瘦”。先来看看时间函数中经常用到的两个数据类型的定义:
// clock_t 的定义
#ifndef _CLOCK_T_DEFINED
typedef long clock_t;
#define _CLOCK_T_DEFINED
#endif
// time_t 的定义
#ifndef _TIME_T_DEFINED
typedef long time_t;
#define _TIME_T_DEFINED
#endif
long 型数据的取值范围是 [-2147483648 +2147483647]。所以,gettimeofday 函数取得的时间最大值为2147483647 + 2147483647 / 1000000 = 2147485794.483647 s,大约为68.096年;
clock函数取得的时间最大值为2147483647 / 1000000 = 2147.483647 s,大约为35.79分钟;
time函数取得的时间最大值为2147483647 s,大约为68年。
这里只是介绍Linux平台下c语言中计算程序运行时间的方法,它们各有利弊,依据自己的需要可以使用对应的方法。在Windows平台下还有其他计算程序运行时间的方法,在此不叙。
Mean Time,GMT)。比如,中国内地的时间与UTC的时差为+8,也就是UTC+8。美国是UTC-5。
秒
typedef long time_t; /* 时间值 */
#define _TIME_T_DEFINED /* 避免重复定义 time_t */
#endif
C++中采用了__time64_t数据类型来保存日历时间,并通过_time64()函数来获得日历时间(而不是通过使用32位字的time()函数),这样就可以通过该数据类型保存3001年1月1日0时0分0秒(不包括该时间点)之前的时间。
并非空指针的话,此函数也会将返回值存到t指针所指的内存。
从午夜算起的时数,范围为0-23
目前月份的日数,范围01-31
代表目前月份,从一月算起,范围从0-11
从1900 年算起至今的年数
一星期的日数,从星期一算起,范围为0-6
从今年1月1日算起至今的天数,范围为0-365
日光节约时间的旗标
char time_str[128] = "2005-10-18 9:37:45";
struct tm tmp;
strptime(time_str, "%Y-%m-%d %H:%M:%S", &tmp);
time_t tm = mktime(&tmp);
将time_t转换成字符串:
time_t tm = time(NULL);
struct tm tmp;
localtime_r(&tm, &tmp);
strftime(buffer, bufferlen, "%Y-%m-%d %H:%M:%S", &tmp);
Mean Time,GMT)。比如,中国内地的时间与UTC的时差为+8,也就是UTC+8。美国是UTC-5。
秒
typedef long time_t; /* 时间值 */
#define _TIME_T_DEFINED /* 避免重复定义 time_t */
#endif
C++中采用了__time64_t数据类型来保存日历时间,并通过_time64()函数来获得日历时间(而不是通过使用32位字的time()函数),这样就可以通过该数据类型保存3001年1月1日0时0分0秒(不包括该时间点)之前的时间。
并非空指针的话,此函数也会将返回值存到t指针所指的内存。
从午夜算起的时数,范围为0-23
目前月份的日数,范围01-31
代表目前月份,从一月算起,范围从0-11
从1900 年算起至今的年数
一星期的日数,从星期一算起,范围为0-6
从今年1月1日算起至今的天数,范围为0-365
日光节约时间的旗标
char time_str[128] = "2005-10-18 9:37:45";
struct tm tmp;
strptime(time_str, "%Y-%m-%d %H:%M:%S", &tmp);
time_t tm = mktime(&tmp);
将time_t转换成字符串:
time_t tm = time(NULL);
struct tm tmp;
localtime_r(&tm, &tmp);
strftime(buffer, bufferlen, "%Y-%m-%d %H:%M:%S", &tmp);