【Linux】CPU时间与处理器耗时
文章结构:
概念与函数
CPU时间(CPU TIME)是指当应用进程启动后,占用CPU进行计算所进行的时间绝对值,或叫时间点。如果进程进入中断、挂起、休眠等行为时,是不占用CPU的,所以CPU时间并不会跟着增加,且进程恢复运行后所获得的CPU时间值与运行中断之前的时间值是连续的,并不会因为运行的暂停而导致CPU时间的跳跃。这是和普通的日常时间值不一样的点。另一个不同的点是CPU时间并不是以秒为单位的,而是处理器的时钟周期为单位。
获取CPU时间的函数#include <time.h>
clock_t clock(void)
该函数返回CPU时间,CPU时间并不一定会从0开始,所以要计算进程的一段代码所消耗的CPU时间记录需要在代码前后获取各自的起始和终止CPU时间,两者之差即为所消耗的CPU时间,即为处理器耗时(Processor Time)。
要将clock_t
转换为秒数,需要除以宏CLOCKS_PER_SEC
生成的值,即为秒数。但要注意的是由于在不同的系统中clock_t
或CLOCKS_PER_SEC
可能为int
也可能为浮点型数值,所以在计算时需要在计算过程中并其强制转换为double
以保证计算结果。
另外需要注意的是clock()
函数所返回的值在32位系统上每72分钟就会恢复原始值一次。clock()
如果执行错误,则会返回-1。
获取CPU时间与更详细的进程时间还有另外一个函数:#include <sys/times.h>
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 */
};
函数times()
会将进程的处理器时间分类。在参数tms
中各字段含义为:tms_utime
:用户层处理器耗时。即执行该进程的实现代码所消耗的CPU时间和。tms_stime
:系统层处理器耗时。即该进程实现的代码调用了系统内核代码,由这部分内核代码执行所消耗的CPU时间和。tms_cutime
:子进程用户层处理器耗时。即如果当前进程通过调用system()
或fork()
函数导致产生新的子进程(数量前后可能为多个),则记录所有子进程的实现代码所消耗的CPU时间和。tms_cstime
:子进程用户层处理器耗时。即如果当前进程通过调用system()
或fork()
函数导致产生新的子进程(数量前后可能为多个),则记录所有子进程实现的代码调用了系统内核代码,由这部分内核代码执行所消耗的CPU时间和。
函数times()
所返回的值单位都是时钟滴哒(clock tick)
数。要获取每秒钟的时钟滴哒可以调用函数sysconf(_SC_CLK_TCK);
来获取。为了保证精度,在计算耗时时,也要在计算过程中将参与的计算值转换为double
以保证计算结果。
函数times()
所返回的值表示过去某个时间点到函数调用此刻的时间消耗长度。所以进程的中断、挂起、休眠等行为仍会导致返回值的持续增加。这一点是与clock()
的不同所在。通常也利用这个特点来计算进程的前后自然时间耗时时长。
在计算时长方面,times()
通过精确到秒以下(可能是毫秒,因为是double类型的计算),但缺点是参数buf
必须不为空;difftime()
由于其参数设定的原因只能精确到秒,能力是最弱的;gettimeofday()
所获取的timeval
则可精确到微秒,只是计算时间差值时麻烦一点。
命令Time
除了在代码中调用函数来计算各种耗时以外,还可能通过命令time
来显示耗时统计。
Time命令原型如下:
time [options] command [arguments...]
options
:time
命令的可选参数,只接受-p
,表示为按标准格式输出。如下:
real: 命令`date`的全部运行时间时长。
user: 命令`date`(包括其子进程,如果有的话)的用户层处理器耗时。
sys : 命令`date`(包括其子进程,如果有的话)的系统层处理器耗时。
command
:time
要统计的命令。[arguments]
:command
命令所接受的参数。
命令Time的输出是可以自定义的,通过设置环境变量
TIMEFORMAT
指定输出所要的格式化结果。
另从man手册中查看Time还具有查看CPU占用率、I/O数据量等功能(可是按文档在Mac上来操作没有成功呀…)
示例代码
接下来的代码将演示:
获取进程id,父进程id,当前线程id
#include <sys/types.h> #include <unistd.h> #include <sys/syscall.h>
pid_t getpid(void); // 获取进程id
pid_t getppid(void); // 获取父进程id
pid_t gettid(void){ // 获取线程id
return syscall(SYS_gettid);
}
这里值得提一下的是getttid()
是系统内核非对外公开的函数。所以这里根据其源码实现利用syscall()
方法来获取线程id。
线程休眠
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
参数seconds
表示线程的休眠时间,单位为秒。在指定的休眠时间内,线程会暂停工作直至时间到期或被中断。
进程生成子进程
为了测试times()
中的tms_cutime
和tms_cstime
,需要创建一个子进程,这里用到system()
函数。#include <stdlib.h>
int system(const char *command);
参数command
为命令,可以是可执行程序路径也可以是系统支持的命令。system()
函数会调用fork()
去创建一个子进程并执行参数command
。在子进程执行期间,父进程所调用的线程是被中断的,不会再进行任何操作直至等到子进程全部执行完毕。子进程所产生的日志输出也将在结束之后才会被打印出来。
CPU时间点的获取与处理器耗时
示例代码中,通过在程序的开头、进行中、结束各个阶段执行clock()
来获取进程当前的CPU时间点,通过减法操作最终得到每个阶段的处理器耗时。
执行完sleep()
的前后CPU时间对比可以验证CPU时间是进程所消耗处理器而产生的连续的时间集合。CPU用了多少就是多少,而与正常的时间流逝无关。
执行完system()
的前后CPU时间对比可以验证子进程的CPU时间并不会被计算在父进程中,可以通过times()
区分。
clock_t c_start = clock();
sleep(2); // 进程进入休眠
clock_t c_mid01 = clock();
doCopyFile(); // 进程进入繁忙的工作状态
clock_t c_mid02 = clock();
system(“”); // 生成子进程
clock_t c_end = clock();
doCopyFile()
函数是复制文件的代码实现,本示例中所复制的文件大小为35M,耗时大概3.5s左右。
具体复制代码实现可以查看之前的文章:【C/C++】文件创建、打开、读、写、复制、关闭、删除等操作汇总
查看进程与子进程的处理器耗时详情
获取耗时详情的前提是要获取每秒时钟滴哒数;而详情则是通过struct tms
的前后对比得出的时间差:
// process_start与后面的process_end用于计算整个进程的耗时
clock_t process_start = times(&sys_t_start);
// 获取每秒的时钟滴哒数
long tck = sysconf(_SC_CLK_TCK);
... ... ...
struct tms sys_t_end;
clock_t process_end = times(&sys_t_end);
printf("4. sys time: user time=%f, stime=%f, cutime=%f, cstime=%f \n",
(sys_t_end.tms_utime - sys_t_start.tms_utime)/(double)tck,
(sys_t_end.tms_stime - sys_t_start.tms_stime)/(double)tck,
(sys_t_end.tms_cutime - sys_t_start.tms_cutime)/(double)tck,
(sys_t_end.tms_cstime - sys_t_start.tms_cstime)/(double)tck);
printf("5. real elapsed time=%f \n", ((process_end - process_start)/(double)tck));
最后看代码的运行结果吧,如下图:
图中,real elapsed time=9.250000
与time
命令所打印出来的real 0m9.249s
相符;user time=3.360000 , cutime=3.380000
两者之和与user 0m6.757s
相符;stime=0.120000 cstime=0.120000
两者之和与sys 0m255s
相符。
数值上不是绝对相等,代码内的自我计算总是会少于time
的值,因为代码内是计算完了之后要打印的,这部分打印及退出的时间是代码的盲区,而time
命令能统计到。
以下为实现代码:
1 | #include <stdio.h> |