19

测量一段代码的执行时间的常见方法

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzI0NjA1MTU5Ng%3D%3D&%3Bmid=2247483963&%3Bidx=1&%3Bsn=50fbd6b859ba624ec81377eb748b9fdd
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文主要适用于 x86-64 体系结构下的 Linux C/C++ 服务器程序。

程序运行的时候,我们经常需要测量某一段代码的执行时间。最简单的做法,自然就是在代码开始的地方获取当前时间 begin_time,在代码结束的地方获取当前时间 end_time,然后计算 end_time - begin_time 即可。

测量代码执行时间的时候需要考虑以下几个问题:

  1. 代价 - 这可能是一个高频操作,获取时间的代价不能太高。

  2. 精度 - 至少是微秒级别。

  3. 稳定 - 如果使用的时钟抖动误差很大,那测量结果往往是不可靠的。

gettimeofday

gettimeofday(2) [1] 这个函数可以获得微秒级别的时间戳。该函数获得的时间是使用墙上时间 xtime 和 jiffies 处理得到的。

墙上时间其实就是实际时间,从 UTC 1970-01-01 00:00:00 开始算起,它是由主板电池供电的 RTC 芯片存储提供。这样即使机器断电了,时间也不用重设。

jiffies 是 Linux 内核启动后的节拍数,在 x86-64 的体系结构下,Linux 内核的节拍频率是 1000,即一个节拍的时间是 1s / 1000 = 1ms。也就是说,jiffies 这个全局变量存储了操作系统启动以来共经历了多少毫秒。

仅仅靠 xtime 和 jiffies 是无法达到微秒级别的精度的。在 Linux 内核中,高精度定时器 hrtimer 模块也会对 xtime 进行修正的,这个模块能够支持纳秒级别的时间精度。关于 hrtimer,我这里就不多介绍了,有兴趣的可以在网上查阅相关资料。

在 x86-64 的系统中,gettimeofday 不是系统调用,其调用成本和普通的用户态函数基本一致。内核采用了“同时映射一块内存到用户态和内核态,数据由内核态维护,用户态拥有读权限”的方式使得该函数调用不需要陷入内核去获取数据。

clock_gettime

clock_gettime(2) [2] 支持获取纳秒级别的时间戳。同时可以通过参数 clk_id 指定获取的时间类型,主要有:

  1. CLOCK_REALTIME - 墙上时间,即纳秒级精度的墙上时间。作用了 gettimeofday 类似。

  2. CLOCK_MONOTONIC - 从系统启动起开始计时的运行时间。

  3. CLOCK_PROCESS_CPUTIME_ID - 本进程执行到当前代码时系统CPU花费的时间。

  4. CLOCK_THREAD_CPUTIME_ID - 本线程执行到当前代码时系统CPU花费的时间。

rdtsc/rdtscp

rdtsc/rdtscp [3] 是 x86 CPU 的指令,含义是 read TSC(Time Stamp Counter) 寄存器。TSC 寄存器在每个 CPU 时钟信号到来时加 1。所以这个数值的递增速度和 CPU 的主频相关。比如,主频为 1M Hz 的 CPU,这个寄存器每秒就递增 1 000 000 次。服务器 x86-64 的 CPU 主频一般都在 1G Hz 以上,所以通过这个指令,我们可以获得纳秒级别的时间精度。

使用 rdtsc 存在一些问题:

  1. CPU 乱序执行使得指令会影响代码执行时间的测量(乱序执行之后,无法保证 rdtsc 指令的执行一定是在业务代码执行的之前和之后)。这个问题可以用 rdtscp 指令来解决。

  2. CPU的运行频率(降频、超频)可能会变化。这个可以在 /proc/cpuinfo 查看 CPU 的 TSC 相关特性,主要是 constant_tsc 和 nonstop_tsc。简单说,有这两个特性的 CPU 就可以认为 TSC 寄存器的时钟信号频率是不变的。

    1. constant_tsc: TSC ticks at a constant rate.

    2. nonstop_tsc: TSC does not stop in C states.

  3. 无法保证每个 CPU 核心的 TSC 寄存器是同步的。比如,线程在 CPU1 获得了代码开始的 begin tick,中间发生上下文切换,恢复后线程在 CPU2 获得了代码结束的 end tick。此时计算用 end tick - begin tick 计算出来的时间是不准确的。这个问题暂时没有好办法可以解决。根据具体的代码逻辑,这个问题可能没什么影响,也可能导致测量结果不准确。

小结

使用 Quick C++ Benchmark [4] 对上面几种方式进行简单的 benchmark。编译参数:GCC 10.1 -std=c++20 -O3 结果如下: aYR36nn.png!mobile

  1. rdtscp 只需要执行一条 CPU 指令读取寄存器,性能上秒杀其他方式,速度大概是其他方式的 45~50 倍。可惜目前还无法解决不同 CPU 之间的 TSC 寄存器的同步问题。

  2. gettimeofday 的性能略优于 clock_gettime,但是差距不是,大概百分之十。

  3. clock_gettime 的 CLOCK_REALTIME 和 CLOCK_MONOTONIC 性能上几乎一样。

测试代码

可以在 Quick C++ Benchmark [5] 运行。

#include <sys/time.h>

static void GetTimeOfDay(benchmark::State& state) {
  for (auto _ : state) {
    struct timeval tv;
    gettimeofday(&tv, nullptr);
  }
}
BENCHMARK(GetTimeOfDay);

static void ClockGetTime_REAL(benchmark::State& state) {
  for (auto _ : state) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
  }
}
BENCHMARK(ClockGetTime_REAL);

static void ClockGetTime_MONOTONIC(benchmark::State& state) {
  for (auto _ : state) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
  }
}
BENCHMARK(ClockGetTime_MONOTONIC);

inline uint64_t GetTickCount() {
  uint32_t lo, hi;
  uint64_t o;
  __asm__ __volatile__("rdtscp" : "=a"(lo), "=d"(hi) : : "%ecx");
  o = hi;
  o <<= 32;
  return (o | lo);
}

static void GetTickCount_rdtscp(benchmark::State& state) {
  for (auto _ : state) {
    uint64_t ticks = GetTickCount();
    benchmark::DoNotOptimize(ticks);
  }
}
BENCHMARK(GetTickCount_rdtscp);

参考资料

[1]

gettimeofday(2): https://man7.org/linux/man-pages/man2/gettimeofday.2.html

[2]

clock_gettime(2): https://man7.org/linux/man-pages/man2/clock_gettime.2.html

[3]

rdtsc/rdtscp: https://en.wikipedia.org/wiki/Time_Stamp_Counter

[4]

Quick C++ Benchmark: https://quick-bench.com/q/DxnMEl4tUb5bAXRqxI1gSmWvUqM

[5]

Quick C++ Benchmark: https://quick-bench.com/q/DxnMEl4tUb5bAXRqxI1gSmWvUqM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK