欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

测量程序运行时间

程序员文章站 2024-01-23 18:04:22
...

测量程序运行时间

由于之前运行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:系统实时时间
...
相关标签: rdtscp 计时

上一篇: Windows系统精准计时

下一篇: