如何精确测量程序运行时间

精确测量C/C++程序运行时间的多种方法 clock()函数是C/C++编程语言中的一个标准库函数,属于<ctime>头文件。它的主要作用是返回程序执行到当前时间所经过的处理器时钟周期数,因此可用于测量程序的运行时间。clock()函数返回的类型是clock_t,与常量结合使用可以转换为实际的秒数。函数是Unix和类Unix操作系统中用于获取当前时间和日期的标准C库函数。这个函数可以返回当前时间的秒数和微秒数,它提供比标准C库中time()函数更高的时间分辨率,因此非常适合于需要精确到毫秒级别的性能测量。 阅读详情

来源:https://www.cnblogs.com/kosmanthus/articles/1423466.html

前言

对于一个嵌入式程序员来说,“我的程序到底运行多快”,是我们最为关心的问题,因为速度,实时性,永远是嵌入式设备性能优化的基本立足点之一。 可惜的是,我们平时常用的测试运行时间的方法,并不是那么精确的。换句话说,想精确获取程序运行时间,不是那么容易的。也许你会想,程序不就是一条条指令 么,每一条指令序列都有固定执行时间,为什么不好算?真实情况下,我们的计算机并不是只运行一个程序的,进程的切换,各种中断,共享的多用户,网络流量, 高速缓存的访问,转移预测等,都会对计时产生影响。

可惜的是,在性能测量领域,我们有gprof,有intel的vtune,却缺少相应 的,广泛流传的参考文献。如果你希望能建立起自己的工具,或者对具体的测量方式感兴趣,那么本文也许会对你有帮助。我想,应该有很多人希望知道计时机制的 原理,因为针对不同的系统,环境,会有不同的解决方案。本文主要针对Linux和X86体系环境,主要思想来源于"Computer System A Programmer's Perspective",夹杂了一些自己的理解,并试图给出我自己写的一个通用测量工具,支持用户自配置。本文有时的对象是程序有时描述对象是进程,这个请自行理解,因为一个程序就是在一个进程里面执行的。
 

进程调度和模式切换

在介绍具体方法之前,先简单说几句。

对 于进程调度来讲,花费的时间分为三部分,第一是计时器中断处理的时间,也就是当且仅当这个时间间隔的时候,操作系统会选择,是继续当前进程的执行,还是切 换到另外一个进程中去。第二是进程切换时间,当系统要从进程A切换到进程B时,它必须先进入内核模式将进程A的状态保存,然后恢复进程B的状态。因此,这 个切换过程是有内核活动来消耗时间的。第三就是进程的具体执行时间了,这个时间也包括内核模式和用户模式两部分,模式之间的切换也是需要消耗时间,不过都 算在进程执行时间中了。

其实模式切换非常费时,这也是很多程序中都要采用缓冲区的原因,例如,如果每读一小段文件什么的就要调用一次 read之类的内核函数,那太受影响了。所以,为了尽量减少系统调用,或者说,减少模式切换的次数,我们向程序(特别是IO程序)中引入缓冲区概念,来缓 解这个问题。

一般来说呢,向处理器发送中断信号的计时器间隔通常是1-10ms,太短,切换太多,性能可能会变差,太长呢,如果在任务间切换频繁,又无法提供在同时执行多任务的假象。这个时间段,也决定了一些我们下面要分析的不同方法衡量时间的差异。
 

方法一:间隔计数

我 们都知道,Linux下有一个命令是专门提供一个进程的运行时间的,也就是time。time可以测量特定进程执行时所需消耗的时间及系统资源等,这个时 间还可以分内核时间和用户态时间两部分呈现给你。它是怎么做到的呢?其实很简单,操作系统本身就是用计时器来记录每个进程使用的累计时间,原理很简单,计 时器中断发生时,操作系统会在当前进程列表中寻找哪个进程是活动的,一旦发现,哟,进程A跑得正欢,立马就给进程A的计数值增加计时器的时间间隔(这也是 引起较大误差的原因,想想)。当然不是统一增加的,还要确定这个进程是在用户空间活动还是在内核空间活动,如果是用户模式,就增加用户时间,如果是内核模 式,就增加系统时间。

原理很简单吧?但是相信一点,越简单的东西,是不会越精确的,人品守恒,能量守恒,难度也当然会守恒了啊。下面就简 单分析一下,为啥这玩意精度不高吧。举个例子,如果我们有一个系统,计时器间隔为10ms,系统里面跑了一个进程,然后我们用这种方法分析时间,测出 70ms,想一想,实际会有几种结果?具体点,我们用这种方法对进程计时,在某个计时器中断时,系统发现,咦,有一个进程开始跑了,好,给进程的计数值加 上10ms。但是实际上呢,这个进程可能是一开始就跑起来了,也肯能是在中断的前1ms才开始跑的。不管是什么原因,总之中断时候它在跑,所以就得加 10ms。当中断发生时发现进程切换了,同理,可能是上一个中断之后1ms进程就切换了,也可能人家刚刚才切换。

所以呢,如果一个进程的 运行时间很短,短到和系统的计时器间隔一个数量级,用这种方法测出来的结果必然是不够准确的,头尾都有误差。不过如果程序的时间足够长,这种误差有时能够 相互弥补,一些被高估一些被低估,平均下来刚好,呵呵。从理论上,我们很难分析这个误差的值,所以一般只有程序到达秒的数量级时,用这种方式测试程序时间 才有意义。

说了半天,难道这方法没优点了?不,这个世界没有纯善,也没有纯恶。这方法最大的优点是,它的准确性不是非常依赖于系统负载。那什么方法依赖于系统负载呢?接下来我们会讲到:)

理论陈述结束,我想应该开始关注实现方法了吧。其实超级简单,两种方法:
 

  1. 直接调用time命令(一堆鸡蛋)
  2. 使用tms结构体和times函数

说说正经点的第二个方法吧。在Linux中,提供了一个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
}

怎么使用就不用这里教了吧?不过要说明一下的是,这里的cutime和cstime,都是对已经终止并回收的时间的累计,也就是说,times不能监视任何正在进行中的子进程所使用的时间。
 

方法二:周期计数


刚 才谈了半天间隔计数的不足之处,哪有不足,那就有弥补的方法,特别实在万能的Linux中:) 为了给计时测量提供更高的准确度,很多处理器还包含一个运行在时钟周期级别的计时器,它是一个特殊的寄存器,每个时钟周期它都会自动加1。这个周期计数器 呢,是一个64位无符号数,直观理解,就是如果你的处理器是1GHz的,那么需要570年,它才会从2的64次方绕回到0,所以你大可不必考虑“万一溢出 怎么办”此类问题。

看到这里,也许你会想,哇塞,很好很强大嘛,时钟周期,这都精确到小数点后面多少位来着了?这下无论是多快的用时多短 的程序,我们也都能进行时间测量了。Ohyeah。等等,刚才我们说过什么来着?守恒定律啊!功能强大的东西,其他方面必有限制嘛。看到上面的介绍,聪明 的你一定能猜出来这种方法的限制是什么了,那就是,hardware dependent。首先,并不是每种处理器都有这样的寄存器的,其次,即使大多数都有,实现机制也不一样,因此,我们无法用统一的,与平台无关的接口来 使用它们。怎么办?这下,就要祭出上古传说中的神器:汇编了。当然,我们在这里实际用的是C语言的嵌入汇编:

void counter( unsigned *hi, unsigned *lo )
{
asm("rdtsc; movl %%edx,%0; movl %%eax, %1"
        : "=r" (*hi), "=r" (*lo)
        :
        : "%edx", "%eax");
}

第一行的指令负责读取周期计数器,后面的指令表示将其转移到指定地点或寄存器。这样,我们将这段代码封装到函数中,就可以在需要测量的代码前后均加上这个函数即可。最后得到的hi和lo值都是两个,除了相减得到间隔值外,还要进行一些处理,在此先按下不表。

不 得不提出的是,周期计数方式还有一个问题,就是我们得到了两次调用counter之间总的周期数,但我们不知道是哪个进程使用了这些周期,或者说处理器是 在内核还是在用户模式中。还记得刚才我们讲间隔计数方式么?这玩意的好处就是它是操作系统控制给进程计时的,我们可以知道具体哪个进程,哪个模式。但是周 期计数只测量经过的时间,他不管你是哪个进程使用的。所以,用周期计数的话,我们必须很小心。举个例子

double time()
{
     start_counter();
     p();
     get_counter();
}

这样一段程序,如果机器的负载很重,会导致P运行时间很长,而其实P函数本身是不需要运行这么长时间的,而是上下文切换等过程将它的时间拖长了。

而且,转移预测(想一想,如果转移方向和目的预测错误)和高速缓存的命中率,对这个计数值也会有影响。通常情况下,为了减少高速缓存不命中给我们程序执行时间带来的影响,可以执行这样的代码:

double time_warm( void )
{
     p();
     start_counter();
     p();
     get_counter();
}

原因不用我再解释了吧?它让指令高速缓存和数据高速缓存都得到了warm-up。

好,接下来又有问题。如果我们的应用,是属于那种每次执行都希望访问新的数据的那种呢?在这种情况下,我们希望让指令高速缓存warm-up,而数据高速缓存不能warm-up,很明显,time_warm函数低估我们的运行时间了。让我们进行进一步修改:

double time_cold( void )
{
     p();
     clear_cache();
     start_counter();
     p();
     get_counter();
}

注意,我们加入了一个清除数据缓存的函数。这个函数的具体实现很简单,依情况而定,比如举个例子

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;
}

具体原理很简单,我们在定义一个数组并在其上执行一个计算,计算过程中的数据会覆盖高速数据缓存中原有的数据。每一次的store和load都会让高速数据缓存cache这个数组,而定义为volatile的tmp则保证这段代码不会被优化。

这样做,是不是就万无一失了呢?不是的,因为大多数处理器,L2高速缓存是不分指令和数据的,这样clear_cache会让所有P的指令也被清除,只不过:L1缓存中的指令还会保留而已。

其实上面提到的诸多原因,都是我们不能控制的,我们无法控制让高速缓存去加载什么,不去加载什么,加载时去掉什么,保留什么。而且,这些误差通常都是会过高估计真实的运行时间。那么具体使用时,有没有什么办法来改善这种情况呢?有,就是The K-Best Measurement Scheme。这玩意其实很麻烦,所以我在具体实践中都不用它,附上一个文档,有兴趣的朋友可以下载下来看一下。

我不喜欢间隔计数的小适用范围,也不喜欢周期计数的麻烦性,相信读到这里的99%的读者也和我一种感受吧。OK,最后我们要介绍的,就是一个可移植性更好,相对较准确的方法。
 

方法三:gettimeofday函数计时


gettimeofday是一个库函数,包含在time.h中。它的功能是查询系统时钟,以确定当前的日期和时间。它很类似于刚才所介绍的周期计时,除了测量时间是以秒为单位,而不是时钟周期为单位的。原型如下:

struct timeval
{
long tv_sec;
long tv_usec;
}

int gettimeofday( struct timeval *tv, NULL )

这 个机制呢,具体的实现方式在不同系统上是不一样的,而且虽然披着一个usec(us)的老虎皮,其实没这么精确。具体的精确程度,是和系统相关的,比如在 Linux下,是用周期计数来实现这个函数的,所以和周期计数的精确度差不多,但是在Windows NT下,使用间隔计数实现的,精确度就很低了(所以啊,万恶的ms啊)。

具体使用的时候,就是开始来一个gettimeofday( tvstart, NULL ),结束来一个gettimeofday( tvend, NULL ),完了sec域和usec域相减的差值就是计时时间。

如何,很方便吧?应该说在Linux下,这是最有效而方便的计时方式了。从测试情况看,精确度也不错。这种价格便宜量又足的东西嘛,大家可以随便多用。
 

总结

这次的总结很简单:没有一个计时方法是完美的,我们所要作的,就是理解本质后,在特定的系统上去寻找特定的好方法。

从零开始搭建GD32F103CBT6开发环境:VSCode配置与实战技巧 本文详细介绍了如何从零开始为国产MCU GD32F103CBT6搭建高效的VSCode开发环境。内容涵盖工具链安装、VSCode核心插件配置、工程目录结构规划、关键配置文件编写以及Makefile实战,旨在帮助开发者摆脱传统IDE束缚,利用现代化工具链提升嵌入式开发效率。 阅读详情

相关推荐

实战指南 | 利用TSMaster的C小程序实时监控DBC报文发送周期

本文提供了一份利用TSMaster的C小程序实时监控DBC报文发送周期的实战指南。通过创建用户变量、编写C小程序核心逻辑并关联图形模块,工程师可以直观地可视化关键报文(如ID 0x64)的发送间隔,从而快速发现周期异常,提升汽车电子开发和测试中总线问题定位的效率与实时性。

weixin_29178069的博客 417

C/C++编程:计算cpp程序运行时间(ms)(单线程&&多线程)

需要c++11支持 #include <chrono> auto start = std::chrono::system_clock::now(); //do something auto end = std::chrono::system_clock::now(); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); std::cout << elapse

OceanStar的博客 3314

Unity中基于SkinMeshRenderer的骨骼动画换装系统优化实践

本文深入探讨了Unity中基于SkinnedMeshRenderer的骨骼动画换装系统优化实践。针对换装卡顿、显示错误等常见问题,文章剖析了SkinnedMeshRenderer的核心数据原理,对比了“数据替换”与“实体替换”两种主流方案的性能差异,并详细阐述了通过骨骼字典缓存、对象池管理、材质合并与Draw Call优化等实战技巧,构建高性能、高稳定性的生产级换装系统。

weixin_29008055的博客 314

C++中几种测试程序运行时间的方法

一、使用GetTickCount()函数    原理:GetTickCount()是获取系统启动后的时间间隔。通过进入函数开始定时,到退出函数结束定时,从而可以判断出函数的执行时间,这种时间也并非是函数或者算法的真实执行时间,因为在函数和算法线程不可能一直占用CPU,对于所有判断执行时间的函数都是一样,不过基本上已经很准确,可以通过查询进行定时。注意:GetTickCount()精确度有限,跟C

幸运的铁匠的博客 1万+

MultiOTP Credential Provider避坑指南:从安装到RDP双因素认证的全流程解析

本文详细解析了MultiOTP Credential Provider从安装到实现RDP双因素认证(MFA)的全流程,重点分享了Docker部署、Active Directory集成及网络级认证(NLA)兼容性等环节的实战避坑经验,旨在帮助运维人员高效、稳定地部署离线双因素认证方案,提升Windows远程桌面安全性。

weixin_29255731的博客 642

MATLAB R2023a下载链接及软件安装教程

MATLAB的基本数据单位是矩阵,它的指令表达式与数学、工程中常用的形式十分相似,故用MATLAB来解算问题要比用C,FORTRAN等语言完成相同的事情简捷得多,并且MATLAB也吸收了像Maple等软件的优点,使MATLAB成为一个强大的数学软件。a是上半年,b是下半年版本,但是有些小弱们总是觉得只要是b一定功能多,这不一定的,同年版本b比a可能稍微全点,不同年的,比如21b,22a,肯定22a要比21b版本高功能多了。10、选择目标文件夹,默认的是C盘,想修改的话可以单击【浏览】,然后单击【下一步】

qq_53545309的博客 5158

Julia并行计算笔记(二)

系列文章(2/5),讲了怎样把函数或表达式放到远程上运行。

iamzhtr的博客 2684

基于改进Unet的多场景水果图像分割与分类研究

本文针对复杂场景下的水果图像分割问题,提出了一种改进Unet模型。通过构建包含8类水果、3种成熟度及多种干扰场景的数据集,采用ResNet50与MobileNetV3作为骨干网络,并在跳跃连接层集成CBAM注意力机制。实验表明,改进模型在测试集上达到91.6%的mIoU,推理速度提升23%,特别在重叠水果和低光照场景表现突出。研究为农业视觉检测提供了有效解决方案。

AI的内部世界的博客 303

【单片机测量程序运行时间的常用方法】

【单片机测量程序运行时间的常用方法】

Sugar_wolf的博客 2476

3、全面掌握QGIS开发:从调试到应用的全方位指南

本文全面介绍了QGIS开发的各个环节,涵盖插件调试、PyQGIS API导航、插件创建与分发、独立地理空间应用开发,以及全局和项目级首选项的管理。通过详细的步骤说明、代码示例和流程图,帮助开发者系统掌握QGIS扩展与集成的核心技能,并展望了未来在功能拓展、跨平台兼容性和技术融合方面的可能性。

hill8的博客 89

【无人机路径规划】基于强化学习实现多无人机路径规划

【无人机路径规划】基于强化学习实现多无人机路径规划

k8291121的博客 1656

华为OD新系统机试真题 2026.5.10 - 美观的灯笼

华为OD新系统机试真题【美观的灯笼】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题【美观的灯笼】

qq_45776114的博客 640

python 精确测量程序运行时间

问题介绍 在python下面有很多计算代码运行时间的方法。最常见的就是time.time()和time.clock()模块。但是有人会问哪一个最好呢?这里众说纷纭,但其实这两个都不是最好的。或者说,你不应该用他们两来测试代码块的运行时间。 因为在不同的系统下,这两个函数具有不同的精度和表现。具体可以参考: Measure time elapsed in Python? Python’s tim...

持久决心的博客 1万+

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

嵌入式系统程序运行时间精确测量技术

程序运行时间测量是嵌入式系统性能优化的基础技术,其核心原理是通过硬件或软件方式捕获代码段的执行时间。在实时系统中,精确的时间测量能有效识别性能瓶颈、验证实时性要求。基于硬件GPIO和逻辑分析仪的测量方法实现了微秒级精度,相比传统软件计时器具有非侵入式、低成本等优势。该方法特别适用于STM32等MCU平台的算法效率评估、延时函数校准等场景,通过脉冲宽度测量转换时间维度,为嵌入式开发提供可靠的性能分析工具。

weixin_35752645的博客 527

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

精确测量程序运行时间

转载请注明出处:http://blog.youkuaiyun.com/qingheuestc, by EinsteinInIct。    作为一个程序员,很多时候都会去关心自己的程序跑的到底有多快,这时就需要对程序的运行时间进行测量。对于不同的程序,它们的运行时间可能相差很大。诸如科学计算类的程序,由于计算量大且算法的并行化比较差,可能几天都跑不完。而有的程序的运行时间可能只有几分钟甚至不到1ms。

qingheuestc的专栏 1862

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Linux C语言程序运行时间精确测量:从time命令到clock_gettime实战

在程序性能优化和系统调优中,精确测量运行时间是基础且关键的环节。其核心原理在于区分墙上时钟时间与CPU时间,前者反映程序从开始到结束的实际耗时,后者则统计进程占用CPU执行指令的时间。理解这一区别对于选择正确的测量工具至关重要,它直接影响到性能分析的有效性和优化方向的准确性。在Linux C语言开发中,常用的技术包括time命令行工具、C标准库的clock()函数以及系统调用gettimeofday()和clock_gettime()。其中,clock_gettime()凭借其高精度和单调性成为现代开发的首

weixin_31856057的博客 354

linux计算程序运行时间

http://www.360doc.com/content/12/0709/22/10363210_223275463.shtml 我们有时需要得到程序的运行时间,但我们也要知道,根本不可能精确测量某一个程序运行的确切时间­[3],文献[4]中说的很明白,现摘录如下。        我们平时常用的测量运行时间的方法并不是那么精确的,换句话说,想精确获取程序运行时间并不是那么容易的。也许你会

liyong2688的专栏 640

linux 统计 程序运行时间

我们有时需要得到程序的运行时间,但我们也要知道,根本不可能精确测量某一个程序运行的确切时间­[3],文献[4]中说的很明白,现摘录如下。        我们平时常用的测量运行时间的方法并不是那么精确的,换句话说,想精确获取程序运行时间并不是那么容易的。也许你会想,程序不就是一条条指令么,每一条指令序列都有固定执行时间,为什么不好算?真实情况下,我们的计算机并不是只运行一个程序的,进程的切换,各种

那些年....的专栏 1407

精准获得程序运行时间

获得精准的程序运行时间@(技术杂项) 用到头文件#include<windows.h> LARGE_INTEGER lFrequency; QueryPerformanceFrequency(&lFrequency); //获取CPU的时钟频率 LARGE_INTEGER lBeginCount; QueryPerformanceCounter(&lBeginCount); //获取CPU计数器数字

进击的ACMer!!! 311

vs2017 c++输出程序运行所用时间

使用GetTickCount:从操作系统启动到现在所经过(elapsed)的毫秒数,它的返回值是DWORD。(精确到毫秒)  //获取程序运行时间  long t1=GetTickCount();//程序段开始前取得系统运行时间(ms)  Sleep(500);  long t2=GetTickCount();();//程序段结束后取得系统运行时间(ms)  cout &lt;&lt; (t2 -...

lingmengxiaotong的博客 6375
上一篇: echo改变输出的颜色
下一篇: 优快云博客,请勿使用默认标题
Colorful_lights
博客等级 码龄10年 27粉丝 38原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值