Linux下clock计时函数学习

2012-12-19 21:12:01

平时在Linux和Winows下都有编码的时候,移植代码的时候免不了发现一些问题。
1. 你到底准不准?关于clock()计时函数
首先是一段简单的测试代码,功能为测试从文本文件读取数据并赋值给向量最后打印输出的运行时间。

int main(int argc, char **argv)
{
clock_t t1=clock();
ifstream in("data.txt");
vector<int> v;
for(int a;in>>a;v.push_back(a));
cout<<v.size()<<endl;
for(int i=0;i<v.size();i++)
cout<<v[i]<<" ";
cout<<endl;
clock_t t2=clock();
cout<<"TotalTime:"<<t2-t1<<"ms"<<endl;
}

这段代码中用了两个clock_t类型的clock()函数来计算程序运行时间,

在windows下编译运行,
TotalTime:465ms

在Linux下编译运行,
TotalTime:420000ms

ok,问题来了,除了正常码农都能理解的系统误差原因,明显是由于clock()函数在不同平台下的返回值不同原因造成的,查clock函数定义有下面的描述:

clock returns the processor time used by program since the beginning of the execution, or -1 if unavailable.

这里提到clock()函数返回的是程序运行过程中耗掉得process time,也就是CPU time。
你以为它返回的是一个标准时间单位,你错了,因为还有下一句描述:
clock() / CLOCKS_PER_SEC is a time in seconds.
CLOCKS_PER_SEC,它用来表示一秒钟会有多少个时钟计时单元,也就是硬件滴答数。
先不管什么叫硬件滴答数,你需要知道clock()是基于时钟计时单元(clock tick)的这个东西来计数的。一个clock tick不是CPU的一个时钟周期,而是C/C++的一个基本计时单位,因此只与编译器有关。在TC2.0中硬件每18.2个滴答是一秒,在VC中硬件每1000个滴答是一秒,在标准POSIX中定义为1000000个滴答为一秒。

ok,知道了这么多,要想让程序正确运行输出,并保证多平台的兼容性,代码作如下处理:
cout<<"TotalTime:"<<(double)( t2-t1)/CLOCKS_PER_SEC<<"s"<<endl;

很简单,程序正确输出之后,你迫不及待的多次运行测试,发现无论如何你的程序都是一个精确到10毫秒的整数,真是因为每次都那么巧么?经搜索发现,标准POSIX平台的clock()只能精确到10ms,至于为什么我目前还不得而知。

2. 做个理性的偏执狂,关于系统计时误差
    如果你是一个偏执狂,当你正为使用clock()函数丢失了几毫秒而懊恼不已时,我却你还是回家陪你老婆逛逛街吧。因为精确测量某一个程序运行的确切时间是根本不可能的,至少现在是这样。我不厌其烦的从其他文章中找到这样的一些话,希望对你理解为何有误差有所帮助。
    文章中提到“我们平时常用的测量运行时间的方法并不是那么精确的,换句话说,想精确获取程序运行时间并不是那么容易的。也许你会想,程序不就是一条条指令么,每一条指令序列都有固定执行时间,为什么不好算?真实情况下,我们的计算机并不是只运行一个程序的,进程的切换,各种中断,共享的多用户,网络流量,高速缓存的访问,转移预测等,都会对计时产生影响。
    对于进程调度来讲,花费的时间分为两部分,第一是计时器中断处理的时间,也就是当且仅当这个时间间隔的时候,操作系统会选择,是继续当前进程的执行还是切换到另外一个进程中去。第二是进程切换时间,当系统要从进程A切换到进程B时,它必须先进入内核模式将进程A的状态保存,然后恢复进程B的状态。因此,这个切换过程是有内核活动来消耗时间的。具体到进程的执行时间,这个时间也包括内核模式和用户模式两部分,模式之间的切换也是需要消耗时间,不过都算在进程执行时间中了”。

ok,读完上面这段话,你应该很清楚的知道一个程序即使每次执行相同的命令,所花费的时间也不一定相同,因为其花费的时间与系统运行相关。原因就在于无论是windows还是linux,都是多任务操作系统。为了让你更明白,现在从另一个角度对执行一个程序所消耗的时间进行分类,如下:

(1) 实际运行时间(real time):从命令行执行到运行终止的消逝时间;

(2) 用户CPU时间(user CPU time):命令在用户态中执行时间的总和;

(3) 系统CPU时间(system CPU time):命令在系统核心态中执行时间的总和。

现在有没有这样的疑问,实际运行时间(1)是不是等于(2)+(3)?对于一个进程而言,除了用户和系统之外难道还有别人么?

答案是肯定的。抬头看看上面提到的进程调度,回头想想你大二学的操作系统课程里的时间片轮转,进程五状态,明白了吧,此时的程序表面在给你执行代码,CPU早跑别人家去了。

ok,现在回头看看开头那段代码的运行结果,clock()算出的时间究竟属于上面的哪一个呢?为此我们设计如下一个实验:
该段代码为测试执行一亿次空循环所消耗的时间

int main( void )
{
clock_t start, finish;
double duration;
long i,j;
start = clock();
for( i=0;i<100;i++){
for( j=0;j<1000000;j++);
}
finish = clock();
duration = (double)(finish- start) / CLOCKS_PER_SEC;
printf( "Time to do %ld empty loops is ",i*j);
printf( "%f seconds\n",duration );
return 0;
}

编译连接
g++ test.cpp -o test
运行程序
./test
显示结果
Time to do 100000000 empty loops is 0.290000 seconds
仅这条结果当然证明不了什么,好吧,我们在其中加一句

sleep(5); // 需要包含头文件<unistd.h>

sleep函数会使程序暂时挂起,也就是阻塞状态,在这5s内,CPU并不在该进程上花费时间。如果最后显示的时间大于5s的话,可以证明clock函数计算的是程序实际运行时间,如果依然接近290ms的话,则是CPU运行时间。

运行程序 ./test

显示结果
Time to do 100000000 empty loops is 0.290000 seconds

一样!现在证明了clock函数是计算的CPU时间,聪明且记忆力好的你此时会发现我有欺骗大众智商的嫌疑,clock的定义里

明确之处计算的是processor time,不就是CPU时间啊,好吧,我就算设计一个实验证实了这个说法没错,但究竟clock函数计算的是哪个CPU时间,暂时是无能为力证明了。

偏执狂你的开始不满意了,究竟有没有办法分别统计一个程序的这三样时间呢?答案是有的。

Linux下有一个很简单很好用的命令就可以来统计程序运行的这三种时间——time命令。此命令的用途在于测量特定指令执行

时所需消耗的时间及系统资源等资讯。如果你只是在优化一小段精简的代码,这对你来说是个好消息。

下面用time命令运行上面的那段测试代码:

运行shell命令

time ./test

显示结果

Time to do 100000000 empty loops is 0.290000 seconds

real 0m0.915s
user 0m0.031s
sys 0m0.266s

现在简单分析结果,终于真相大白了。上面程序中使用clock()函数计算出来的时间就为总的CPU时间。也就是说,clock函数不能区分用户空间和内核空间。
    上面介绍的time命令能测量特定进程执行时所消耗的时间,它是怎么做到的呢?方法叫做间隔计数。如果你对他感兴趣的话,

可以继续读读下面的介绍,你会发现原理其实很简单。

操作系统用计时器来记录每个进程使用的累计时间,计时器中断发生时,操作系统会在当前进程列表中寻找哪个进程是活动的,一

旦发现进程A正在运行立马就给进程A的计数值增加计时器的时间间隔(这也是引起较大误差的原因)。当然不是统一增加的,还要确定这个进程是在用户空间活动还是在内核空间活动,如果是用户模式,就增加用户时间,如果是内核模式,就增加系统时间。这种方法的原理虽然简单但不精确。如果一个进程的运行时间很短,短到和系统的计时器间隔一个数量级,用这种方法测出来的结果必然是不够精确的,头尾都有误差。不过,如果程序的时间足够长,这种误差有时能够相互弥补,一些被高估一些被低估,平均下来刚好。从理论上很难分析这个误差的值,所以一般只有程序达到秒的数量级时用这种方法测试程序时间才有意义。这种方法最大的优点是它的准确性不是非常依赖于系统负载。

3. 事实果真如此么?关于”三多“话题

多核多进程多线程技术的发展使得运算效率不断提高的同时,也给码农们带来新的烦恼。

3.1 多核计算
上面已经知道了clock函数的实现是基于时钟计时单元的。问题就出在了cpu的时钟计时单元上。当采用多核cpu时,进程或线程调

用clock,记录了当前核时钟。但在下次调用clock之前很可能发生cpu调度,进程或线程被调度到其他cpu上运行。这导致两次取得

计时单元并不是同一个cpu的,产生计时错误。但究竟这个误差有多大,有待实验论证。

3.2 多进程计算

上面通过time函数进行了验证,clock函数计算的时间貌似是等于用户CPU时间+系统CPU时间。果真如此么?我们再次对测试代码

进行修改,将循环次数减少至一千次,并在空循环里加一句

system("cd");

这一句用于模拟子进程的运行,这里图方便选择系统进程作为子进程。

运行shell命令

time ./test

显示结果

Time to do 1000 empty loops is 0.010000 seconds

real 0m3.492s
user 0m0.512s
sys 0m2.972s

这个实验说明了,clock函数并没有考虑CPU被子进程消耗的时间。

3.3 多线程计算

上面提到了三个时间Real time, User time和Sys time。real time > user time + sys time 这种关系始终成立么?

答案是否定的。原因就在于并行计算。现在再次回忆一下这三种时间的概念:

Real指的是实际经过的时间,User和Sys指的是该进程使用的CPU时间。
1. Real是墙上时间(wall clock time),也就是进程从开始到结束所用的实际时间。这个时间包括其他进程使用的时间片和进程阻塞的时间(比如等待I/O完成)。
2. User指进程执行用户态代码(核心之外)所使用的时间。这是执行此进程所消耗的实际CPU时间,其他进程和此进程阻塞的时间并不包括在内。
3. Sys指进程在内核态消耗的CPU时间,即在内核执行系统调用所使用的CPU时间。
那么,什么情况下进程开始到结束所经过的时间会比进程所消耗的用户时间和系统时间(user time + sys time)小呢?
User+Sys为进程所使用的实际CPU时间。在多处理器的系统上,一个进程如果有多个线程或者有多个子进程并行执行,就可能导致Real time比CPU time(User + Sys time)要小,这是很容易理解的。

Linux下clock计时函数学习相关推荐

  1. linux getline参数,Linux下的getline函数

    最近在做国嵌的mp3项目,在mp3主控程序中用到了这个函数,挺好使的,在这里记录一下.注意是linux下的,不是C++中的. 函数原型 ssize_t getline(char **lineptr, ...

  2. [转帖]关于Linux下的icotl函数

    关于Linux下的icotl函数 最近接触android开发,因为有时间所以就关注了下android的源码,在跟踪源码过程中到最后都会遇到icotl函数,虽然在Symbian中曾经遇到过RSocket ...

  3. Linux下进程通信知识点学习笔记(一)

    4种主要事件导致进程创建: 系统的初始化: 执行了正在运行的进程所调用的进程创建系统调用: 用户请求创建一个进程: 一个批处理作业的初始化: 进程的终止: 正常退出: 出错退: 严重错误: 被其他进程 ...

  4. linux下rpm,yum学习

    linux下RPM及yum学习 linux中程序管理程序主要分为两类 dpkg(Debian Packager):debian,Ubuntu,Knoppix         rpm(Redhat Pa ...

  5. sleep头文件linux,Linux下的sleep函数 要用的话得包涵什么头文件啊?

    Linux下的sleep函数 要用的话得需要#include sleep把进程的运行状态改为睡眠,将其从系统可执行队列去掉,这样系统就不会调度到该进程,不会分配CPU时间片,同时根据该进程的睡眠时间, ...

  6. linux下的加密解密学习

    linux下的加密解密学习 加密/解密:         加密协议:加密解密使用同一秘钥:3des,aes         公钥加密:公钥私钥对         数字签名,密钥交换          ...

  7. linux父进程中显示子进程pid,请教linux下c语言函数fork父进程打印子进程的PID

    请教linux下c语言函数fork父进程打印子进程的PID 关注:296  答案:2  信息版本:手机版 解决时间 2019-01-14 04:55 雨不眠的下 2019-01-13 12:23 用于 ...

  8. Linux下的延时函数

    Linux下的延时函数 1.sleep函数 头文件:#include<unistd.h> 功能:执行挂起操作一段时间,以秒为单位 一般形式:unsigned sleep(unsigned ...

  9. algorithm头文件下的常用函数-学习笔记

    algorithm头文件下的常用函数-学习笔记 max(x,y),min(x,y),abs(x) swap(x,y) reverse(it,it2) next_permutation() fill( ...

  10. Linux下无法使用 itoa 函数的解决方法

    起因 在Linux环境下进行C++编程,使用 itoa 函数时出现以下错误: ' itoa ' was not declared in this scope. 翻阅Linux下的 stdlib.h 头 ...

最新文章

  1. 修正r s分析法matlab程序,关于R/S分析程序用法
  2. Android最佳性能实践(二)——分析内存的使用情况
  3. 博客园电子月刊[第一期]
  4. ITK:过滤图像FilterImage
  5. dsp实现快速傅里叶的C语言程序,DSP-快速傅立叶变换(FFT)算法实验
  6. 基于 FFMPEG 的音频编解码(二):音频解码
  7. 什么是今日头条下拉词下拉框?
  8. python处理HTML的函数库,【整理】关于Python中的html处理库函数BeautifulSoup使用注意事项...
  9. 用R演示逻辑回归过程-值得收藏
  10. Android必知必会-使用Intent打开第三方应用及验证可用性
  11. 项目-字典-更新字典分组
  12. linux 下安装wps
  13. php 开发商城 注意,thinkPHP商城公告功能开发问题分析
  14. 聊天中批判性思维的应用
  15. gwr模型用什么做_迷你世界微缩模型还能这样用,玩家自己就能做皮肤
  16. teambition、Tower、worktile 、trello 等任务管理工具哪个好?
  17. ME3616 NBIOT模组对接OneNET教程以及STM32代码
  18. 2018年9月份面试小记
  19. 图像处理中的用于消除高斯噪声的加法运算
  20. eclipse中@override报错的解决方法

热门文章

  1. LoadRunner 录制常见错误解决方法
  2. 与IDE相关的Attribute属性(C#)
  3. php提取bing背景,PHP代码获取bing每日背景
  4. 关系型数据库管理系统(RDBMS)与非关系型数据库(NoSQL)之间的区别
  5. iOS 使用Socket
  6. 乌克兰发布新版《网络安全战略》
  7. Nginx https configuration backed Certbot
  8. 修改笔记-批量去除附件售价
  9. MOSS 2010 无法同步用户配置文件
  10. 遇到网络问题,要逐段排查