测量程序运行时间

测量程序运行时间

由于之前运行fxmark时遇到rdtscp,之前只用过gettimeofday,故学习一下程序测量时间

参考:https://yq.aliyun.com/articles/15114,https://www.cnblogs.com/kosmanthus/articles/1423466.html

小结:

  1. 精度排序:rdtsc > clock_gettime(ns) > gettimeofday(us) > time(m)
  2. clock_gettime和gettimeofday实现细节有待考证
  3. 程序采用缓冲区的原因?
  4. RTC:实时钟,主板电池提供; jiffies:内核启动的节拍数,与计时中断请求(IRQ)有关;rdtsc:CPU计时器
计时 优点 缺点
time 准确性不依赖于负载 不精确
rdtscp 精确 硬件支持,需汇编嵌入,且不知道对多核CPU核心每个TSC寄存器是否同步
gettimeofday 次精确,usec usec不精确
clock_gettime 次精确,nsec 不知道是否精确

学习笔记

进程调度时间花费 = 计时器中断处理时间(选择是否切换进程)+ 进程切换时间 + 进程执行时间(包括内核模式和用户模式,这2种模式间的切换也耗时)

程序采用缓冲区的原因:模式切换非常耗时,这是程序采用缓冲区的原因,例如,如果每读一小段文件什么的就要调用一次 read之类的内核函数,那太受影响了。所以,为了尽量减少系统调用,或者说,减少模式切换的次数,我们向程序(特别是IO程序)中引入缓冲区概念,来缓解这个问题。

1. time (间隔计数)

原理:操作系统本身就是用计时器来记录每个进程使用的累计时间,原理很简单, 计时器中断发生时,操作系统会在当前进程列表中寻找哪个进程是活动的,一旦发现,哟,进程A跑得正欢,立马就给进程A的计数值增加计时器的时间间隔(这也是 引起较大误差的原因,想想)。当然不是统一增加的,还要确定这个进程是在用户空间活动还是在内核空间活动,如果是用户模式,就增加用户时间,如果是内核模 式,就增加系统时间。

缺点:不精确,尤其是对运行时间短的进程;times不能监视正在进行中的子进程时间

优点:准确性不依赖于系统负载。

2种使用方式

  1. linux命令行命令: time ./test
  2. 使用tms结构体和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
}

2. rdtsc (周期计数)

在处理器中包含的时钟周期级别的计时器,是一个特殊的寄存器

优点:精确

缺点:hadware dependent,需使用汇编嵌入; 不知道是哪个进程使用了这些周期,不知道是内核还是在用户模式

这个计时器的反对说法还有:

  • 从Pentium Pro开始引入的CPU乱序执行使得指令重排序会影响
  • CPU的频率可能会变化,比如节能模式。
  • 无法保证每个CPU核心的TSC寄存器是同步的

重排序这个好说,使用cpuid指令保序就行,如果CPU比较新的话直接用rdtscp指令就好,这个已经是保序的指令了。至于频率变化问题,如果是较新的CPU,可以在/proc/cpuinfo文件里看看,如果tsc相关的特性有__constant_tsc和nonstop_tsc__存在,就不用担心这个了。前者Constant TSC means that the TSC does not change with CPU frequency changes, however it does change on C state transitions,后者The Non-stop TSC has the properties of both Constant and Invariant TSC。不过,多个CPU之间的不同步这里并没有解决。有意思的是,前面的gettimeofday(2)在返回时对xtime和jiffies进行修正时,也有使用TSC寄存器的值。

void counter( unsigned *hi, unsigned *lo )
{
asm("rdtsc; movl %%edx,%0; movl %%eax, %1"
        : "=r" (*hi), "=r" (*lo)
        :
        : "%edx", "%eax");
}
//第一行的指令负责读取周期计数器,后面的指令表示将其转移到指定地点或寄存器。这样,我们将这段代码封装到函数中,就可以在需要测量的代码前后均加上这个函数即可
double time_cold( void )
{
     p(); //warm-up
     clear_cache(); // 希望指令高速缓存warm-up,而数据高速缓存不能warm-up
     start_counter();
     p();
     get_counter();
}

// 清除数据缓存的函数
计算过程中数据会覆盖高速缓存中原有数据,volatile的tmp保证代码不会被优化
volatile int tmp;
static int dummy[N];      // N是你需要清理缓存的字节数

void clear_cache( void )
{
     inti, sum = 0;
     for( i=1;i<N;i++ )
          dummy[i] = 2;
     for( i=1;i<N;i++ )
          sum += dummy[i];
     tmp = sum;
}

3. gettimeofday函数

精度:虽然结构为usec但其实不精确,与不同系统上具体实现方式有关。
Linux中使用周期计数来实现,精度高;windows NT使用间隔计数,精度低。

但文章1说,函数获得的系统时间是使用墙上时间xtime和jiffies处理得到,墙上时间是由主板电池供电的RTC(实时钟),jiffies是linux内核启动后的节拍数。这两个来源无法到达us精度.

PS: 运行时的 Linux 内核会周期性地发出计时中断请求(IRQ),每秒钟发出的计时中断请求数称之为节拍率,每次计时中断周期称之为节拍,实际计时中断次数称之为节拍数

#include <time.h>
原型
struct timeval
{
long tv_sec;
long tv_usec;
}
int gettimeofday( struct timeval *tv, NULL )

使用
gettimeofday(tvstart,NULL)
.....
gettimeofday(tvend,NULL)
计时时间 = tvend - tvstart中的sec域和usec域

4. clock_gettime函数

精度:比gettimeofday()精度高

#include <time.h>

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

struct timespec {
    time_t   tv_sec;        /* seconds */
    long     tv_nsec;       /* nanoseconds */
};
clk_id指定获取的时间类型
CLOCK_REALTIME:系统实时时间
...

猜你喜欢

转载自blog.csdn.net/u010521366/article/details/88933822