程序/进程执行时间分析(user cpu time, system cpu time, elpapsed time)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/niaolianjiulin/article/details/82726126

1. 背景

近日做程序优化任务,代码不长,时间测试起来需要在for循环里加,破坏了程序执行的流水线,导致时间不准。网上查看了些相关资料,学到了一些东西,虽然对本任务没用,但对于程序/进程执行时间可加深理解。

首先,需要明白的是:

我们平时常用的测量运行时间的方法并不是那么精确的,换句话说,想精确获取程序运行时间并不是那么容易的。也许你会想,程序不就是一条条指令么,每一条指令序列都有固定执行时间,为什么不好算?真实情况下,我们的计算机并不是只运行一个程序的,进程切换,各种中断,共享的多用户,网络流量,高速缓存的访问,转移预测等,都会对计时产生影响。

这个是比较好理解的,通常实际中跑程序跑几次,每次的时间是略有差异的。那么程序的运行时间究竟是怎样的呢?

2. 执行时间

2.1 构成

程序运行时是以进程的形式,因此讨论是在进程层面。

  • 真实时间:进程从开始执行到最后结束的时间,包括阻塞 + 就绪 + 运行的时间。称为 wall clock time/墙上时钟时间/elpapsed time,是我们跑程序实际等待的时间;

  • 系统cpu时间:用户进程获得CPU资源后,在内核态的执行时间,如write,read等系统调用;

  • 用户cpu时间:用户进程获得CPU资源后,在用户态执行的时间,主要是我们自己编写的代码;

其中可大致认为:

真实时间 = 阻塞时间 + 就绪时间 +CPU运行时间

CPU运行时间 = 用户CPU时间 + 系统CPU时间

之所以说大致可以认为,是因为时间测试中方法不能做到十分精确,但主要因素就是这几个。

2.2 如何获取时间

2.2.1 clock()函数

作用:获取CPU运行时间(用户CPU时间 + 系统CPU时间),程序中常用前后2次差值来获取时间。

差值表示该进程经过了多少个clock。1个clock是1个计时基本单元。注意其不是cpu cycle(1/主频)。在Hi3559开发板上是1000000个clock/秒,即1us/clock。各平台因编译器不同而不同。出于平台移植性,使用宏CLOCKS_PER_SEC获取该值并参与时间的计算。

// 计算进程运行时间,单位:s
clock_t t1 = clock();
proc();
clock_t t2 = clock();
TotalTime = (double)(t2 - t1) / CLOCKS_PER_SEC;

2.2.2 gettimeofday()函数

作用:获取真实时间(us),目前项目中测试时间用的是该函数。

struct timeval start, end;
long long proc_time;
gettimeofday(&start, NULL);
proc();
gettimeofday(&end, NULL);
proc_time = (end.tv_sec - start.tv_sec) * 10E6 + (end.tv_usec - start.tv_usec);
printf("%lld us", proc_time);

ps:如何判断获取的是真实时间还是CPU时间?
在两次取时间中间加各sleep(5),如果差值时间增加了5s,则是真实时间,反之若差别很小则是CPU时间。因为sleep的功能是把当前进程挂起,此时CPU干别的事了。

2.2.3 time命令

运行程序时,shell中采用:

# time ./test_exe
real 0m 5.69s
user 0m 0.08s
sys  0m 0.61s

即可打印出该程序的真实时间、CPU用户时间、CPU系统时间。上述如果用clock统计,则测试的是后两者之和real=user+sys=0.69s,它不会把挂起的5s算进去。若用gettimeofday统计,则测试的是真实时间。

2.2.4 times()函数

clock()函数和gettimeofday()函数均不能区分CPU用户时间和系统时间。time命令虽然可以区分CPU用户时间和系统时间,但是从程序整体层面统计的。若我们想获取程序中某一段代码的CPU用户时间和系统时间,如何处理?采用times()函数即可。

note:times函数获取的是进程CPU时间(CPU用户时间、CPU系统时间)自进程run后发生的tick数,所谓的滴答,CPU的一个基本计时单位,宏定义_SC_CLK_TCK表示1s包含的滴答数,在Hi3559开发板上测试100个tick/秒,即10ms/tick。

// 包含头文件
#include<sys/times> 

// 函数原型
clock_t times(struct  tms * buf);

// 数据结构
struct tms {
    clock_t tms_utime;   /* user   time */
    clock_t tms_stime;   /* system time */
    clock_t tms_cutime;  /* user   time of children */
    clock_t tms_cstime;  /* system time of children */
};

struct tms start, end;

if (times(&start) == -1)    
    err_exit("times error");

proc();

if (times(&end) == -1)  
    err_exit("times error");

int tick_per_sec = sysconf(_SC_CLK_TCK);

printf("user time: %d s\n", (end.tms_utime - start.tms_utime) / (tick_per_sec));
printf("syst time: %d s\n", (end.tms_stime - start.tms_stime) / (tick_per_sec));

note:linux中clock函数并不能统计子进程的时间,而times函数是可以获取子进程时间的。struct tms 的最后两个成员变量tms_cutime和tms_cstime是用来记录子进程(们)的system cpu time 和 user cpu time,其值是在父进程中执行wait 或 waitpid 时开始记录,等到wait返回后停止记录。

时间构成讨论

1、2.1节中时间构成是在单核情况下讨论,真实时间通常大于CPU时间(CPU用户时间 + CPU系统时间),因为存在进程切换等诸多复杂开销。

2、多核情况下带来了困扰,使这个问题不好讨论。比如两个CPU并行做proc,则CPU时间之和可能会大于真实时间。

3、了解了上述时间测试的内容,可以帮助我们在实际测试中选择最合适的方法,虽然它不是如此精确,但主要问题还是可以抓住,为我们提供有效的时间参考。

参考

[1] https://www.cnblogs.com/wfwenchao/p/5195022.html
[2] https://blog.csdn.net/youjun9007228198/article/details/26090423

猜你喜欢

转载自blog.csdn.net/niaolianjiulin/article/details/82726126
今日推荐