计算程序运行时间 time t clock t
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
计算程序运行时间time t clock t
计算程序运行时间(time_t,clock_t)
转载我们有时需要得到程序的运行时间,但我们也要知道,根本不可能精
确测量某一个程序运行的确切时间,文献中说的很明白,现摘录如下。
我们平时常用的测量运行时间的方法并不是那么精确的,换句话说,想精
确获取程序运行时间并不是那么容易的。也许你会想,程序不就是一条条指令么,每一条指令序列都有固定执行时间,为什么不好算?真实情况下,我们的计算机并不是只运行一个程序的,进程的切换,各种中断,共享的多用户,网络
流量,高速缓存的访问,转移预测等,都会对计时产生影响。
文献中还提到:对于进程调度来讲,花费的时间分为两部分,第一是计时
器中断处理的时间,也就是当且仅当这个时间间隔的时候,操作系统会选择,
是继续当前进程的执行还是切换到另外一个进程中去。第二是进程切换时间,
当系统要从进程A切换到进程B时,它必须先进入内核模式将进程A的状态保存,然后恢复进程B的状态。因此,这个切换过程是有内核活动来消耗时间的。具体到进程的执行时间,这个时间也包括内核模式和用户模式两部分,模式之
间的切换也是需要消耗时间,不过都算在进程执行时间中了。
那么有哪些方法能统计程序的运行时间呢?通过查找一些资料并结合自己的实践体会,摘录和总结了下面几种方法。
一、Linux的time命令
Linux系统下统计程序运行实践最简单直接的方法就是使用time命令,文
献[1,2]中详细介绍了time命令的用法。此命令的用途在于测量特定指令执行
时所需消耗的时间及系统资源等资讯,在统计的时间结果中包含以下数据:
实际时间(real time):从命令行执行到运行终止的消逝时间;
用户CPU时间(user CPU time):命令执行完成花费的系统CPU时间,即命令在用户态中执行时间的总和;
系统CPU时间(system CPU time):命令执行完成花费的系统CPU时间,即命令在核心态中执行时间的总和。
其中,用户CPU时间和系统CPU时间之和为CPU时间,即命令占用CPU执行的时间总和。实际时间要大于CPU时间,因为Linux是多任务操作系统,往往在执行一条命令时,系统还要处理其他任务。另一个需要注意的问题是即使每次执行相同的命令,所花费的时间也不一定相同,因为其花费的时间与系统运行相关。
二、间隔计数
上面介绍的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。
三、周期计数
为了给计时测量提供更高的准确度,很多处理器还包含一个运行在始终周
期级别的计时器,它是一个特殊的寄存器,每个时钟周期它都会自动加1。这
个周期计数器呢,是一个64位无符号数,直观理解,就是如果你的处理器是
1GHz的,那么需要570年,它才会从2的64次方绕回到0,所以你大可不必考虑溢出的问题。但是这种方法是依赖于硬件的。首先,并不是每种处理器都有
这样的寄存器的;其次,即使大多数都有,实现机制也不一样,因此,我们无
法用统一的,与平台无关的接口来使用它们。这下,就要使用汇编了。当然,
在这里实际用的是C语言的嵌入汇编:
void counter(unsigned*hi,unsigned*lo){asm("rdtsc;movl%%edx,%0;movl%%eax,%1″:"=r"(*hi),"=r"(*lo)::"%edx","%eax");}
第一行的指令负责读取周期计数器,后面的指令表示将其转移到指定地点
或寄存器。这样,我们将这段代码封装到函数中,就可以在需要测量的代码前
后均加上这个函数即可。最后得到的hi和lo值都是两个,除了相减得到间隔
值外,还要进行一些处理,在此不表。
不得不提出的是,周期计数方式还有一个问题,就是我们得到了两次调用counter之间总的周期数,但我们不知道是哪个进程使用了这些周期,或者说处理器是在内核还是在用户模式中。间隔计数的好处就是它是操作系统控制给进程计时的,我们可以知道具体哪个进程呢个模式;但是周期计数只测量经过的时间,他不管是哪个进程使用的。所以,用周期计数的话必须很小心。举个例子:
double time()
{
start_counter();
p();
get_counter();
}
这样一段程序,如果机器的负载很重,会导致p运行时间很长,而其实p 函数本身是不需要运行这么长时间的,而是上下文切换等过程将它的时间拖长了。
而且,转移预测和高速缓存的命中率,对这个计数值也会有影响。通常情况下,为了减少高速缓存不命中给我们程序执行时间带来的影响,可以执行这样的代码:
double time_warm(void)
{
p();
start_counter();
p();