linux下获取系统启动时间毫秒_测量一段代码的执行时间的常见方法

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

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

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

  1. 代价 - 这可能是一个高频操作,获取时间的代价不能太高。
  2. 精度 - 至少是微秒级别。
  3. 稳定 - 如果使用的时钟抖动误差很大,那测量结果往往是不可靠的。

gettimeofday

gettimeofday(2) 这个函数可以获得微秒级别的时间戳。该函数获得的时间是使用墙上时间 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) 支持获取纳秒级别的时间戳。同时可以通过参数 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 是 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 对上面几种方式进行简单的 benchmark。 编译参数:GCC 10.1 -std=c++20 -O3 结果如下:

c3933e7d6752786788a0785ba81249c5.png
  1. rdtscp 只需要执行一条 CPU 指令读取寄存器,性能上秒杀其他方式,速度大概是其他方式的 45~50 倍。可惜目前还无法解决不同 CPU 之间的 TSC 寄存器的同步问题。
  2. gettimeofday 的性能略优于 clock_gettime,但是差距不是,大概百分之十。
  3. clock_gettime 的 CLOCK_REALTIME 和 CLOCK_MONOTONIC 性能上几乎一样。

测试代码

可以在 Quick C++ Benchmark 运行。

#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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值