




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、如何精确测量程序运行时间如何精确测量程序运行时间 2011-04-24 20 : 18如何精确测量程序运行时间、,- 、-刖言对于一个嵌入式程序员来说,我的程序到底运行多快,是我们最为关心 的问题,因为速度,实时性,永远是嵌入式设备性能优化的基本立足点之一。 可惜的是,我们平时常用的测试运行时间的方法,并不是那么精确的。换句话 说,想精确获取程序运行时间,不是那么容易的。也许你会想,程序不就是一 条条指令么,每一条指令序列都有固定执行时间,为什么不好算?真实情况下,我们的计算机并不是只运行一个程序的,进程的切换,各种中断,共享的多用 户,网络流量,高速缓存的访问,转移预测等,都会对计时产生影响
2、。可惜的是,在性能测量领域,我们有 gprof,有in tel的vtu ne,却缺少相 应的,广泛流传的参考文献。如果你希望能建立起自己的工具,或者对具体的 测量方式感兴趣,那么本文也许会对你有帮助。我想,应该有很多人希望知道 计时机制的原理,因为针对不同的系统,环境,会有不同的解决方案。本文主 要针对Linux和X86体系环境,主要思想来源于Computer System AProgrammers Perspective,夹杂了一些自己的理解,并试图给出我自己写 的一个通用测量工具,支持用户自配置。本文有时的对象是程序有时描述对象 是进程,这个请自行理解,因为一个程序就是在一个进程里面执行的
3、。进程调度和模式切换在介绍具体方法之前,先简单说几句。对于进程调度来讲,花费的时间分为三部分,第一是计时器中断处理的时 间,也就是当且仅当这个时间间隔的时候,操作系统会选择,是继续当前进程 的执行,还是切换到另外一个进程中去。第二是进程切换时间,当系统要从进 程A切换到进程B时,它必须先进入内核模式将进程 A的状态保存,然后恢复 进程B的状态。因此,这个切换过程是有内核活动来消耗时间的。第三就是进 程的具体执行时间了,这个时间也包括内核模式和用户模式两部分,模式之间 的切换也是需要消耗时间,不过都算在进程执行时间中了。其实模式切换非常费时,这也是很多程序中都要采用缓冲区的原因,例如, 如果每读
4、一小段文件什么的就要调用一次 read之类的内核函数,那太受影响了。 所以,为了尽量减少系统调用,或者说,减少模式切换的次数,我们向程序(特别是10程序)中引入缓冲区概念,来缓解这个问题。一般来说呢,向处理器发送中断信号的计时器间隔通常是1-10ms,太短,切换太多,性能可能会变差,太长呢,如果在任务间切换频繁,又无法提供在 同时执行多任务的假象。这个时间段,也决定了一些我们下面要分析的不同方 法衡量时间的差异。方法一:间隔计数我们都知道,Linux下有一个命令是专门提供一个进程的运行时间的,也 就是time。time可以测量特定进程执行时所需消耗的时间及系统资源等,这个 时间还可以分内核时间
5、和用户态时间两部分呈现给你。它是怎么做到的呢?其实很简单,操作系统本身就是用计时器来记录每个进程使用的累计时间,原理很 简单,计时器中断发生时,操作系统会在当前进程列表中寻找哪个进程是活动 的,一旦发现,哟,进程 A跑得正欢,立马就给进程 A的计数值增加计时器的 时间间隔(这也是引起较大误差的原因,想想)。当然不是统一增加的,还要确 定这个进程是在用户空间活动还是在内核空间活动,如果是用户模式,就增加 用户时间,如果是内核模式,就增加系统时间。原理很简单吧?但是相信一点,越简单的东西,是不会越精确的,人品守恒, 能量守恒,难度也当然会守恒了啊。下面就简单分析一下,为啥这玩意精度不 高吧。举个例
6、子,如果我们有一个系统,计时器间隔为 10ms系统里面跑了一 个进程,然后我们用这种方法分析时间,测出70ms想一想,实际会有几种结果?具体点,我们用这种方法对进程计时,在某个计时器中断时,系统发现,咦, 有一个进程开始跑了,好,给进程的计数值加上10ms但是实际上呢,这个进程可能是一开始就跑起来了,也肯能是在中断的前1ms才开始跑的。不管是什么原因,总之中断时候它在跑,所以就得加 10ms当中断发生时发现进程切换 了,同理,可能是上一个中断之后 1ms进程就切换了,也可能人家刚刚才切换。所以呢,如果一个进程的运行时间很短,短到和系统的计时器间隔一个数 量级,用这种方法测出来的结果必然是不够准
7、确的,头尾都有误差。不过如果 程序的时间足够长,这种误差有时能够相互弥补,一些被高估一些被低估,平 均下来刚好,呵呵。从理论上,我们很难分析这个误差的值,所以一般只有程 序到达秒的数量级时,用这种方式测试程序时间才有意义。说了半天,难道这方法没优点了 ?不,这个世界没有纯善,也没有纯恶。这 方法最大的优点是,它的准确性不是非常依赖于系统负载。那什么方法依赖于 系统负载呢?接下来我们会讲到:)理论陈述结束,我想应该开始关注实现方法了吧。其实超级简单,两种方法:直接调用time命令(一堆鸡蛋)使用tms结构体和times函数说说正经点的 第二个方法吧。在Linux中,提供了一个times函数,原型
8、是clock_t times(struct tms*buf)这个tms的结构体为struct tmsclock_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 childre n怎么使用就不用这里教了吧?不过要说明一下的是,这里的 cutime和 cstime,都是对已经终止并回收的时间的累计,也就是说,times不能监视任何正在进行中的子进程
9、所使用的时间。方法二:周期计数冈財谈了半天间隔计数的不足之处,哪有不足,那就有弥补的方法,特别 实在万能的Linux中:)为了给计时测量提供更高的准确度,很多处理器还包含 一个运行在时钟周期级别的计时器,它是一个特殊的寄存器,每个时钟周期它 都会自动加1。这个周期计数器呢,是一个 64位无符号数,直观理解,就是如 果你的处理器是1GHz的,那么需要570年,它才会从2的64次方绕回到0, 所以你大可不必考虑万一溢出怎么办此类问题。看到这里,也许你会想,哇塞,很好很强大嘛,时钟周期,这都精确到小 数点后面多少位来着了 ?这下无论是多快的用时多短的程序,我们也都能进行时 间测量了。Ohyeah等等
10、,刚才我们说过什么来着?守恒定律啊!功能强大的东 西,其他方面必有限制嘛。看到上面的介绍,聪明的你一定能猜出来这种方法 的限制是什么了,那就是,hardware dependent。首先,并不是每种处理器都 有这样的寄存器的,其次,即使大多数都有,实现机制也不一样,因此,我们 无法用统一的,与平台无关的接口来使用它们。怎么办?这下,就要祭出上古传说中的神器:汇编了。当然,我们在这里实际用的是C语言的嵌入汇编:void counter(un sig ned*hi,u nsig ned*lo)asm(rdtsc ; movl%edx,%; movl%eax,%1:=r(*hi),=r(*lo):%
11、edx,%eax);第一行的指令负责读取周期计数器,后面的指令表示将其转移到指定地点 或寄存器。这样,我们将这段代码封装到函数中,就可以在需要测量的代码前后均加上这个函数即可。最后得到的hi和Io值都是两个,除了相减得到间隔值外,还要进行一些处理,在此先按下不表。不得不提出的是,周期计数方式还有一个问题,就是我们得到了两次调用 counter之间总的周期数,但我们不知道是哪个进程使用了这些周期,或者说 处理器是在内核还是在用户模式中。还记得刚才我们讲间隔计数方式么?这玩意的好处就是它是操作系统控制给进程计时的,我们可以知道具体哪个进程,哪 个模式。但是周期计数只测量经过的时间,他不管你是哪个进
12、程使用的。所以, 用周期计数的话,我们必须很小心。举个例子double time()start_c oun ter();P();get_c oun ter();这样一段程序,如果机器的负载很重,会导致P运行时间很长,而其实P函数本身是不需要运行这么长时间的,而是上下文切换等过程将它的时间拖长 了。而且,转移预测(想一想,如果转移方向和目的预测错误)和高速缓存的命 中率,对这个计数值也会有影响。通常情况下,为了减少高速缓存不命中给我 们程序执行时间带来的影响,可以执行这样的代码:double time_warm(void)P();start_c oun ter()?p();get_c oun t
13、er();原因不用我再解释了吧?它让指令高速缓存和数据高速缓存都得到了warm-up。好,接下来又有问题。如果我们的应用,是属于那种每次执行都希望访问 新的数据的那种呢?在这种情况下,我们希望让指令高速缓存warm-up,而数据高速缓存不能warm-up,很明显,time_warm函数低估我们的运行时间了。让我 们进行进一步修改:double time_cold(void)P();clear_cache();start_c oun ter();P();get_c oun ter();注意,我们加入了一个清除数据缓存的函数。这个函数的具体实现很简单, 依情况而定,比如举个例子volatile i
14、nt tmpstatic int dummyN ; N是你需要清理缓存的字节数void clear_cache(void)inti,sum=0 ;for(i=1 ; i N ; i+)dummyi=2 ;for(i=1 ; i N ; i+)sum+=dummyi;tmp=sum具体原理很简单,我们在定义一个数组并在其上执行一个计算,计算过程中的数据会覆盖高速数据缓存中原有的数据。每一次的 store和load都会让高 速数据缓存cache这个数组,而定义为volatile 的tmp则保证这段代码不会被 优化。这样做,是不是就万无一失了呢?不是的,因为大多数处理器,L2高速缓 存是不分指令和数
15、据的,这样 clear_cache会让所有P的指令也被清除,只不 过:L1缓存中的指令还会保留而已。其实上面提到的诸多原因,都是我们不能控制的,我们无法控制让高速缓 存去加载什么,不去加载什么,加载时去掉什么,保留什么。而且,这些误差 通常都是会过高估计真实的运行时间。那么具体使用时,有没有什么办法来改 善这种情况呢?有,就是 The K-Best Measurement Scheme。这玩意其实很麻烦, 所以我在具体实践中都不用它,附上一个文档,有兴趣的朋友可以下载下来看 一下。我不喜欢间隔计数的小适用范围,也不喜欢周期计数的麻烦性,相信读到 这里的99%勺读者也和我一种感受吧。0K最后我们
16、要介绍的,就是一个可移 植性更好,相对较准确的方法。方法三:gettimeofday 函数计时gettimeofday是一个库函数,包含在time.h中。它的功能是查询系统时 钟,以确定当前的日期和时间。它很类似于刚才所介绍的周期计时,除了测量 时间是以秒为单位,而不是时钟周期为单位的。原型如下:struct timevallong tv_sec ;long tv_usec ;int gettimeofday(struct timeval*tv,NULL)这个机制呢,具体的实现方式在不同系统上是不一样的,而且虽然披着一 个usec(us)的老虎皮,其实没这么精确。具体的精确程度,是和系统相关的, 比如在Linux下,是用周期计数来实现这个函数的,所以和周期计数的精确度 差不多,但是在 Windows NT下,使用间隔
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026届甘肃省白银市平川区中恒学校化学高三上期末综合测试模拟试题含解析
- 信息技术学业讲解
- 西方医学与精神科护理学
- 小升初古诗词选择题专项练习(含答案)
- 食品超微粉碎技术
- 软件项目验收汇报
- 兽医临床诊断技术
- 小学生关联词讲解
- 2026届四川省绵阳市三台中学化学高一上期末复习检测模拟试题含解析
- 如何制作图文讲解
- 《广联达培训教程》课件
- 减少门诊投诉PDCA课件
- 职业暴露与防护41p
- 医疗废物处理登记表
- 二手房屋买卖物品交接清单
- 左手流程-右手人才-章义伍
- 桥梁安全事故案例警示
- 智慧树创意学经济答案-2018创意学经济期末答案
- YY 0054-2023血液透析设备
- 黄冈市临床重点专科申报-模板-副本
- SB/T 10460-2008商用电开水器
评论
0/150
提交评论