深入理解计算机系统–测量程序执行时间

  • Post author:
  • Post category:其他


理论基础

在进行系统时间获取并用于程序性能分析时,我们需要考虑精确而有效的时间获取方法。

在《深入理解计算机系统》这本书中谈到,目前进行时间测量的系统接口和方法有两种:

通过间隔计数的方法:

OS维护着每个进程使用的用户时间量和系统时间量的计数值,当计时器中断发生时,OS会确定哪个进程是活动的,并且对那个进程的一个计数值增加计时器间隔时间。这时会根据进程的状态,如果是内核态就增加系统时间,否则增加用户时间。

这种计时方式并不管在这个时间段(两个计时器中断之间)内到底用了多少时间,所以不够准确。但是从长时间考虑,这个平均值还是可以接受的。

利用周期计数器的方法:

很多CPU包含了一个运行在时钟周期级的计时器。这个计时器其实是个特殊的寄存器,每个时钟周期会加1。这个工具可以用来测量一个程序执行中两个不同点之间经过的时间。就是基于系统时间点的测量,而在这两点之间的情况并不关心。这样导致了多任务系统上,这种计时器只能用于对比而无法直接表示进程时间消耗。所以其受上下文切换、高速缓存的影响。

通过上面的比较和介绍,我们可以看到,两种方法各有优劣、是从不同的方向来进行时间测量的。

所以,这本书在章节后面给出了一个协议:



如果程序X预期的运行时间很长(运行时间远远大于间隔计数的时间段),可以使用间隔计数的方式。



如果X预期运行时间大概在0.01~1.0s之间,那么在负载很轻的系统上,就需要使用周期计时的方式。在这里强调负载轻,主要原因是害怕上下文切换等因素的影响。



如果X预期运行时间小于0.01s,那么就使用周期计时的方式。这时候,由于时间很短,不用担心负载问题,上下文切换等因素起到的影响就很小了。

下面分析我们通常使用的:

———————————–

clock_gettime:

.save   {r4, r7}

stmfd   sp!, {r4, r7}

ldr     r7, =__NR_clock_gettime

swi     #0

ldmfd   sp!, {r4, r7}

movs    r0, r0

bxpl    lr

b       __set_syscall_errno

.fnend

———————————–

gettimeofday:

.save   {r4, r7}

stmfd   sp!, {r4, r7}

ldr     r7, =__NR_gettimeofday

swi     #0

ldmfd   sp!, {r4, r7}

movs    r0, r0

bxpl    lr

b       __set_syscall_errno

.fnend

———————————–

这两个函数都是直接进行了系统调用。

关于时间源这类的时间调用是复杂的,我也没有做这方面工作的经验,不敢乱言。

使用说明请参考:

http://www.360doc.com/content/11/0715/09/1317564_133662142.shtml

gettimeofday()提供了微秒级的精确度

1、头文件 <time.h>

2、函数原型

int gettimeofday(struct timeval *tv, struct timezone *tz);

gettimeofday()会把目前的时间由tv所指的结构返回,当地时区的信息则放到tz所指的结构中(可用NULL)。

参数说明:

timeval结构定义为:

struct timeval

{

long tv_sec; /*秒*/

long tv_usec; /*微秒*/

};

timezone 结构定义为:

struct timezone

{

int tz_minuteswest; /*和Greenwich 时间差了多少分钟*/

int tz_dsttime; /*日光节约时间的状态*/

};

上述两个结构都定义在/usr/include/sys/time.h。tz_dsttime 所代表的状态如下

DST_NONE /*不使用*/

DST_USA /*美国*/

DST_AUST /*澳洲*/

DST_WET /*西欧*/

DST_MET /*中欧*/

DST_EET /*东欧*/

DST_CAN /*加拿大*/

DST_GB /*大不列颠*/

DST_RUM /*罗马尼亚*/

DST_TUR /*土耳其*/

DST_AUSTALT /*澳洲(1986年以后)*/

返回值: 成功则返回0,失败返回-1,错误代码存于errno。附加说明EFAULT指针tv和tz所指的内存空间超出存取权限。

#include<stdio.h>

#include<time.h>

int main(void)

{

struct timeval tv;

struct timezone tz;

gettimeofday (&tv , &tz);

printf(“tv_sec; %d/n”, tv,.tv_sec) ;

printf(“tv_usec; %d/n”,tv.tv_usec);

printf(“tz_minuteswest; %d/n”, tz.tz_minuteswest);

printf(“tz_dsttime, %d/n”,tz.tz_dsttime);

return 0;

}

clock_gettime( ) 提供了纳秒级的精确度

1、头文件 <time.h>

2、编译&链接。在编译链接时需加上 -lrt ;因为在librt中实现了clock_gettime函数

3、函数原型

int clock_gettime(clockid_t clk_id, struct timespect *tp);

参数说明:

clockid_t clk_id 用于指定计时时钟的类型,有以下4种:

CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户该成其他,则对应的时间相应改变

CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响

CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间

CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间

struct timespect *tp用来存储当前的时间,其结构如下:

struct timespec

{

time_t tv_sec; /* seconds */

long tv_nsec; /* nanoseconds */

};

返回值。0成功,-1失败

#include<stdio.h>

#include<time.h>

int main()

{

struct timespec ts;

clock_gettime(CLOCK_REALTIME, &ts);

printf(“CLOCK_REALTIME: %d, %d”, ts.tv_sec, ts.tv_nsec);

clock_gettime(CLOCK_MONOTONIC, &ts);//打印出来的时间跟 cat /proc/uptime 第一个参数一样

printf(“CLOCK_MONOTONIC: %d, %d”, ts.tv_sec, ts.tv_nsec);

clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts);

printf(“CLOCK_PROCESS_CPUTIME_ID: %d, %d”, ts.tv_sec, ts.tv_nsec);

clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);

printf(“CLOCK_THREAD_CPUTIME_ID: %d, %d”, ts.tv_sec, ts.tv_nsec);

printf(“/n%d/n”, time(NULL));

return 0;

}

/proc/uptime里面的两个数字分别表示:

the uptime of the system (seconds), and the amount of time spent in idle process (seconds).

把第一个数读出来,那就是从系统启动至今的时间,单位是秒