如何精确测量一段代码的执行时间

简介:

最近在工作中遇到了需要精确测量一段C代码执行时间的需求,大家给出的方案有以下三种:

  • gettimeofday(2)
  • rdtsc/rdtscp
  • clock_gettime(2)

下面我们就逐一介绍下这三种方案的用法和限制,主要的关注点是准确性、精度和调用成本,讨论环境是运行在Intel x86上的Linux x86_64系统,内核的版本号高于2.6.32。

gettimeofday(2)

首先是gettimeofday(2),函数原型如下:


#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);

struct timeval {
    time_t      tv_sec;     /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};

struct timezone {
    int tz_minuteswest;     /* minutes west of Greenwich */
    int tz_dsttime;         /* type of DST correction */
};
 

从结构体定义山看,这个函数获取到的时间精度是微秒(us,10^-6s)。这个函数获得的系统时间是使用墙上时间xtime和jiffies处理得到的。墙上时间从UTC 1970-01-01 00:00:00开始,由主板电池供电的RTC(实时钟)芯片存储。jiffies是Linux内核启动后的节拍数,Linux内核从2.5版内核开始把频率从100调高到1000,即系统运行频率为1s/1000=1ms(毫秒)。由此可见,仅仅使用这两个来源是无法达到us的精度的。不过在Linux内核中,高精度定时器 hrtimer(High Resolution Timer)模块也会对xtime进行修正的,这个模块甚至支持ns(纳秒,10^-9)的时间精度。

Linux x86_64系统中,gettimeofday的实现采用了“同时映射一块内存到用户态和内核态,数据由内核态维护,用户态拥有读权限”的方式使得该函数调用不需要陷入内核去获取数据,即Linux x86_64位系统中,这个函数的调用成本和普通的用户态函数基本一致(小于1ms)。

总体上来说,微秒级别对于一般的时间获取已经足够,这个函数用在日志输出的时间戳上也很常用,不过对于精度要求很高的场合还是略有些欠缺。

rdtsc/rdtscp

接下来是rdtsc这个CPU指令。这个指令的含义是read tsc寄存器,即time stamp counter寄存器的值。从Pentium处理器开始,Intel的很多80x86微处理器都引入64bit的TSC寄存器,用于时间戳计数器。该寄存器在每个时钟信号到来时加1。那么这个数值的递增就和CPU的主频相关了,主频为1M Hz的处理器这个寄存器每秒就递增1,000,000次。而rdtsc指令把tsc寄存器的数值读出来,数值的低32位存放在eax寄存器中,高32位存放在edx寄存器中。那么很容易就能用gcc的内联汇编写出来读取这个数值的代码:


typedef unsigned long long cycles_t;
inline cycles_t currentcycles() {
    cycles_t result;
    __asm__ __volatile__("rdtsc" : "=A" (result));
    return result;
}
 

但是这个计时方式在网上很容易找到一些反对的说法,常见的说法有以下几种:

  1. 从Pentium Pro开始引入的CPU乱序执行使得指令重排序会影响。
  2. CPU的频率可能会变化,比如节能模式。
  3. 无法保证每个CPU核心的TSC寄存器是同步的。

重排序这个好说,使用cpuid指令保序就行,如果CPU比较新的话直接用rdtscp指令就好,这个已经是保序的指令了。至于频率变化问题,如果是较新的CPU,可以在/proc/cpuinfo文件里看看,如果tsc相关的特性有constant_tscnonstop_tsc存在,就不用担心这个了。前者Constant TSC means that the TSC does not change with CPU frequency changes, however it does change on C state transitions,后者The Non-stop TSC has the properties of both Constant and Invariant TSC。不过,多个CPU之间的不同步这里并没有解决。有意思的是,前面的gettimeofday(2)在返回时对xtime和jiffies进行修正时,也有使用TSC寄存器的值。

顺便说一下,我们的场景是单核心测试一段代码的执行时间。多次运行获得运行时间即可,不是持续长期运行的产品代码,所以用rdtscp指令是可以的。

clock_gettime(2)

最后是clock_gettime(2),其原型如下:


#include <time.h>

int clock_gettime(clockid_t clk_id, struct timespec *tp);

struct timespec {
    time_t   tv_sec;        /* seconds */
    long     tv_nsec;       /* nanoseconds */
};
 

从结构体定义上来看,这是一个ns(纳秒,10^-9)级别精度的时间获取函数。clk_id参数指定获取的时间类型,有以下取值:

  • CLOCK_REALTIME 系统实时时间,从UTC 1970-01-01 00:00:00开始
  • CLOCK_MONOTONIC 从系统启动起开始计时的运行时间,不计算休眠时间
  • CLOCK_MONOTONIC_RAW (since Linux 2.6.28; Linux-specific)类似CLOCK_MONOTONIC,但是基于原始硬件数据,不受NTP时间变动影响
  • CLOCK_PROCESS_CPUTIME_ID 本进程执行到当前代码时系统CPU花费的时间
  • CLOCK_THREAD_CPUTIME_ID 本线程执行到当前代码时系统CPU花费的时间

从参数上看,平时获取时间使用第一个CLOCK_REALTIME参数即可,用这个参数的话有点类似gettimeofday(2),但是精度要高一些(10^-9 vs 10^-6)。事实上当时间类型是CLOCK_PROCESS_CPUTIME_IDCLOCK_THREAD_CPUTIME_ID时,clock_gettime(2)也有利用rdtsc指令来获取时间。具体的调用成本我没有试验,参考资料里有人做了相关的实验并给出了相关的测试数据,可以参考下。

参考资料:
[1] Linux man pages.
[2] 《Combined Volume Set of Intel® 64 and IA-32 Architectures Software Developer’s Manuals》2B
[3] 《How to Benchmark Code Execution Times on Intel® IA-32 and IA-64 Instruction Set Architectures》Gabriele Paoloni
[4] http://stackoverflow.com/questions/6814792/why-is-clock-gettime-so-erratic


目录
相关文章
|
缓存 架构师 算法
【Conan 入门教程 】深入理解 Conan 2.X 中的 self.source_folder
【Conan 入门教程 】深入理解 Conan 2.X 中的 self.source_folder
310 1
|
10天前
|
数据采集 人工智能 运维
2025年企业数据系统建设方案全景测评:案例详解与选型推荐
2025年,数字化转型进入深水区,数据治理成核心驱动力。瓴羊Dataphin、腾讯WeData、华为DataArts Studio等平台,助力企业破解数据孤岛、治理低效难题。其中,阿里云旗下瓴羊Dataphin源自阿里巴巴实践,服务超5万家企业,覆盖零售、金融、制造等20行业,以AI驱动、湖仓一体、全链路治理能力获DAMA等多项大奖,成为企业数据中台建设优选方案。
|
Java Sentinel
【熔断限流组件resilience4j和hystrix】
【熔断限流组件resilience4j和hystrix】
451 0
|
12月前
|
缓存 NoSQL Serverless
云数据库Tair:从稳定低延时缓存到 Serverless KV
本次分享聚焦云数据库Tair的使用,涵盖三部分内容:1) Tair概览,介绍其作为稳定低延时缓存及KV数据库服务的特点和优势;2) 稳定低延迟缓存技术,探讨如何通过多线程处理、优化内核等手段提升性能与稳定性;3) 从缓存到Serverless KV的演进,特别是在AI大模型时代,Tair如何助力在线服务和推理缓存加速。Tair在兼容性、性能优化、扩缩容及AI推理加速方面表现出色,满足不同场景需求。
|
人工智能 Cloud Native Serverless
2024云栖大会资料精选,《云原生+AI核心技术&最佳实践》PPT全量放送!
关注阿里云云原生公众号,后台回复:2024 云栖大会,即可免费下载云原生云栖大会核心资料合集。
3034 35
|
9月前
|
监控 Ubuntu Linux
Windows11 WSL2 Ubuntu编译安装perf工具
通过以上步骤,你已经在Windows 11的WSL2中成功编译并安装了 `perf`工具。尽管在WSL2中可能会遇到一些限制,但大部分基本性能分析功能应当可以正常使用。使用 `perf`进行性能分析,可以帮助你更好地理解和优化系统及应用程序的性能。
703 14
|
12月前
|
Linux
【Linux】System V信号量详解以及semget()、semctl()和semop()函数讲解
System V信号量的概念及其在Linux中的使用,包括 `semget()`、`semctl()`和 `semop()`函数的具体使用方法。通过实际代码示例,演示了如何创建、初始化和使用信号量进行进程间同步。掌握这些知识,可以有效解决多进程编程中的同步问题,提高程序的可靠性和稳定性。
643 19
|
机器学习/深度学习 算法 Ubuntu
【ROS_Driver驱动真实UR机械臂】
【ROS_Driver驱动真实UR机械臂】
2215 0
|
机器学习/深度学习 算法 物联网
LISA微调技术解析:比LoRA更低的显存更快的速度
LISA是Layerwise Importance Sampling for Memory-Efficient Large Language Model Fine-Tuning的简写,由UIUC联合LMFlow团队于近期提出的一项LLM微调技术,可实现把全参训练的显存使用降低到之前的三分之一左右,而使用的技术方法却是非常简单。