跳转至

延迟测量(时钟周期)

1. 重要方法

// 从 TSC(时间戳计数器)寄存器读取值并返回一个 uint64_t 类型的值来表示经过的
// CPU 时钟周期
inline auto Common::rdtsc() noexcept {
  // 定义无符号整数 lo 和 hi 用于存储从 TSC 寄存器读取的值的低 32 位和高 32 位
  unsigned int lo, hi;
  // 使用汇编指令 "rdtsc" 读取 TSC 寄存器的值,将低 32 位存储到 lo,高 32
  // 位存储到 hi
  __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi));
  // 将高 32 位左移 32 位后与低 32 位合并,返回一个 64 位的表示经过 CPU
  // 时钟周期的值
  return ((uint64_t)hi << 32) | lo;
}

2. 重要宏定义

// 开始延迟测量,使用 rdtsc() 函数获取当前 CPU 时钟周期数
#define START_MEASURE(TAG) const auto TAG = Common::rdtsc()

// 结束延迟测量,计算与开始测量时的时钟周期差并记录日志
#define END_MEASURE(TAG, LOGGER)                                              \
  do {                                                                        \
    const auto end = Common::rdtsc();                                         \
    LOGGER.log("% RDTSC " #TAG " %\n", Common::getCurrentTimeStr(&time_str_), \
               end - TAG);                                                    \
  } while (false)

// 记录当前纳秒时间戳
#define TTT_MEASURE(TAG, LOGGER)                                            \
  do {                                                                      \
    const auto TAG = Common::getCurrentNanos();                             \
    LOGGER.log("% TTT " #TAG " %\n", Common::getCurrentTimeStr(&time_str_), \
               TAG);                                                        \
  } while (false)

3. 性能检测方式的工作机制及实现细节

  1. rdtsc 函数:利用汇编指令 rdtsc 从 TSC 寄存器中读取值,将低 32 位和高 32 位合并成一个 64 位的 uint64_t 类型值,代表经过的 CPU 时钟周期。这是整个性能检测的基础,用于获取精确的 CPU 时钟周期数。
  2. START_MEASURE 宏:在需要开始测量延迟的地方使用该宏,它会调用 rdtsc 函数获取当前的 CPU 时钟周期数,并创建一个指定名称(TAG)的常量变量存储该值,用于后续计算延迟。
  3. END_MEASURE 宏:在需要结束测量延迟的地方使用该宏,它再次调用 rdtsc 函数获取当前的 CPU 时钟周期数,计算与开始测量时存储的值的差值,得到延迟的 CPU 时钟周期数,并通过日志记录器(LOGGER)记录当前时间字符串和延迟值。
  4. TTT_MEASURE 宏:用于记录当前的时间戳(以纳秒为单位),通过调用 Common::getCurrentNanos() 函数获取当前时间,然后使用日志记录器记录当前时间字符串和该时间值。

4. 一个优秀的 Intel Linux Invariant TSC CPU 高精度计时器实现示例

来源:https://github.com/a858438680/TSCTimer

#pragma once

#include <atomic>
#include <bitset>
#include <chrono>
#include <cstring>
#include <tuple>
#include <vector>

namespace TSC {

struct TimerHelper {
  static uint64_t average(uint64_t low, uint64_t high) {
    return low + (high - low) / 2;
  }

  static uint64_t rdtscp() {
    unsigned int A;
    return __builtin_ia32_rdtscp(&A);
  }

  static uint64_t rdtsc() { return __builtin_ia32_rdtsc(); }

  static std::tuple<uint64_t, std::chrono::steady_clock::time_point>
  get_tsc_ns_pair() {
    constexpr int try_times = 5;
    uint64_t tsc_arr[try_times + 1];
    std::chrono::steady_clock::time_point ns_arr[try_times];

    tsc_arr[0] = rdtsc();
    for (int i = 0; i < try_times; ++i) {
      ns_arr[i] = std::chrono::steady_clock::now();
      tsc_arr[i + 1] = rdtsc();
    }

    auto min_tsc_diff = tsc_arr[1] - tsc_arr[0];
    auto ave_tsc = average(tsc_arr[0], tsc_arr[1]);
    auto ns = ns_arr[0];

    for (int i = 1; i < try_times; ++i) {
      auto tsc_diff = tsc_arr[i + 1] - tsc_arr[i];
      if (tsc_diff < min_tsc_diff) {
        min_tsc_diff = tsc_diff;
        ave_tsc = average(tsc_arr[i], tsc_arr[i + 1]);
        ns = ns_arr[i];
      }
    }

    return {ave_tsc, ns};
  }

  static void calibrate() {
    auto [end_tsc, end_time] = TimerHelper::get_tsc_ns_pair();
    auto& [begin_tsc, begin_time] = TimerHelper::base_point;
    std::chrono::nanoseconds duration = end_time - begin_time;
    auto scale = static_cast<double>(duration.count()) /
                 static_cast<double>(end_tsc - begin_tsc);
    double expected = 0.;
    cycle_to_ns_scale.compare_exchange_strong(expected, scale,
                                              std::memory_order_relaxed);
  }

  static std::tuple<uint64_t, std::chrono::steady_clock::time_point> base_point;
  static std::atomic<double> cycle_to_ns_scale;
};

inline std::tuple<uint64_t, std::chrono::steady_clock::time_point>
    TimerHelper::base_point = TimerHelper::get_tsc_ns_pair();
inline std::atomic<double> TimerHelper::cycle_to_ns_scale{0.};

struct RDTSC {};
struct RDTSCP {};

template <typename T>
struct is_ratio : std::false_type {};

template <std::intmax_t Num, std::intmax_t Denom>
struct is_ratio<std::ratio<Num, Denom>> : std::true_type {};

template <typename T>
concept Ratio = is_ratio<T>::value;

template <typename T>
concept Number = std::is_integral_v<T> || std::is_floating_point_v<T>;

template <typename T>
concept RDTBase = std::is_same_v<T, RDTSC> || std::is_same_v<T, RDTSCP>;

template <size_t N, Ratio Period = std::ratio<1>, Number Rep = double,
          RDTBase RDTType = RDTSC>
class Timer : protected TimerHelper {
 public:
  Timer() { memset(&cycles_arr_, 0, sizeof(cycles_arr_)); }

  struct end_tag_t {};

  template <typename... Ns>
    requires((std::is_convertible_v<Ns, size_t> ||
              std::is_same_v<Ns, end_tag_t>) &&
             ...) &&
            (((std::is_same_v<end_tag_t, Ns> ? 1 : 0) + ...) <= 1) &&
            (((std::is_constructible_v<Ns, size_t> ? 1 : 0) + ...) <= N)
  void start(Ns... track_types) {
    ;
    if constexpr (std::is_same_v<RDTType, RDTSC>) {
      auto tsc = TimerHelper::rdtsc();
      start_impl(tsc, [](auto arg) constexpr {
        if constexpr (std::is_same_v<end_tag_t, decltype(arg)>) {
          return arg;
        } else {
          return static_cast<size_t>(arg);
        }
      }(track_types)...);
    } else {
      auto tsc = TimerHelper::rdtscp();
      start_impl(tsc, [](auto arg) constexpr {
        if constexpr (std::is_same_v<end_tag_t, decltype(arg)>) {
          return arg;
        } else {
          return static_cast<size_t>(arg);
        }
      }(track_types)...);
    }
  }

  template <typename... Ns>
    requires((std::is_convertible_v<Ns, size_t>) && ...) && (sizeof...(Ns) <= N)
  void end(Ns... track_types) {
    if constexpr (std::is_same_v<RDTType, RDTSC>) {
      auto tsc = TimerHelper::rdtsc();
      end_impl(tsc, static_cast<size_t>(track_types)...);
    } else {
      auto tsc = TimerHelper::rdtscp();
      end_impl(tsc, static_cast<size_t>(track_types)...);
    }
  }

  std::chrono::duration<Rep, Period> get(size_t i) {
    auto scale = TimerHelper::cycle_to_ns_scale.load(std::memory_order_relaxed);
    if (scale == 0.) {
      TimerHelper::calibrate();
      while ((scale = TimerHelper::cycle_to_ns_scale.load(
                  std::memory_order_relaxed)) == 0.);
    }
    std::chrono::nanoseconds result{
        static_cast<std::chrono::nanoseconds::rep>(cycles_arr_[i] * scale)};
    return std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(
        result);
  }

  static size_t size() { return N; }

  uint64_t get_cycle(size_t i) { return cycles_arr_[i]; }

  static constexpr auto stop = end_tag_t{};

 private:
  uint64_t last_tsc_arr_[N];
  uint64_t cycles_arr_[N];

  template <typename... Ns>
  void start_impl(uint64_t tsc, size_t first_start_type, Ns... start_types) {
    last_tsc_arr_[first_start_type] = tsc;
    start_impl(tsc, start_types...);
  }

  void start_impl(uint64_t tsc) {}

  template <typename... Ns>
  void start_impl(uint64_t tsc, end_tag_t, Ns... end_types) {
    end_impl(tsc, end_types...);
  }

  template <typename... Ns>
  void end_impl(uint64_t tsc, size_t first_start_type, Ns... start_types) {
    cycles_arr_[first_start_type] += tsc - last_tsc_arr_[first_start_type];
    end_impl(tsc, start_types...);
  }

  void end_impl(uint64_t tsc) {}
};

}  // namespace TSC
#include <iostream>
#include <thread>

#include "timer.h"

void some_work(int ms) {
  std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

int main() {
  // 创建一个可以追踪2个区间的计时器,结果以毫秒(double类型)返回。
  // 我们使用更精确的 RDTSCP 指令。
  TSC::Timer<2, std::milli, double, TSC::RDTSCP> timer;

  // --- 计时单个操作 ---
  timer.start(0);
  some_work(50);
  timer.end(0);

  std::cout << "操作 0 耗时: " << timer.get(0).count() << " ms\n";
  std::cout << "操作 0 周期数: " << timer.get_cycle(0) << "\n\n";

  // --- 使用 'stop' 标签计时背靠背的操作 ---
  // 启动操作1的计时,然后立即执行一些工作
  timer.start(1);
  some_work(20);

  // 这个单一调用会以最小的开销停止计时器1,并启动计时器0
  timer.start(0, timer.stop, 1);

  some_work(30);
  timer.end(0);  // 停止计时器0

  std::cout << "操作 1 (累积) 耗时: " << timer.get(1).count() << " ms\n";
  std::cout << "操作 0 (累积) 耗时: " << timer.get(0).count() << " ms\n";

  return 0;
}

5. rdtscp

rdtscp 相比 rdtsc 优势:自带弱序列化(减少它自己之前的指令重排)、返回当前核心 APIC ID(可校验跨核)。rdtsc 必须依赖 mfence。

uint64_t rdtsc_stable() {
  uint32_t lo, hi;
  asm volatile(
      "mfence\n"  // 内存屏障,确保顺序执行
      "rdtsc\n"   // 读取TSC
      : "=a"(lo), "=d"(hi));
  return ((uint64_t)hi << 32) | lo;
}

uint64_t rdtscp_stable(uint32_t* aux) {
  uint32_t lo, hi;
  asm volatile(
      "rdtscp\n"  // 读取TSC,等待前面指令完成
      "lfence\n"  // 阻止CPU提前执行后续指令
      : "=a"(lo), "=d"(hi), "=c"(*aux)
      :
      : "memory"  // 保留,约束编译器重排
  );
  return ((uint64_t)hi << 32) | lo;
}

uint64_t rdtscp_simple() {
  uint32_t aux;
  return rdtscp(&aux);
}

#include <cpuid.h>

bool has_rdtscp_support() {
  uint32_t eax, ebx, ecx, edx;
  __cpuid(0x80000001, eax, ebx, ecx, edx);  // 扩展功能查询
  return (edx & (1 << 27)) != 0;            // EDX第27位为1则支持
}

注意: ● 若需严格阻止后续指令重排,在 rdtscp 后加 lfence。 ● 用 APIC ID 校验计时过程是否跨核。

6. 利用 rdtscp 特性实现的计时器示例

#ifndef HARDWARE_TIMER_H
#define HARDWARE_TIMER_H

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <thread>
#include <vector>

#ifdef _WIN32
#include <intrin.h>
#else
#include <cpuid.h>
#include <x86intrin.h>
#endif

/**
 * @brief 基于RDTSCP指令的高精度计时器
 *
 * 支持多种TSC环境:
 * 1. Invariant-TSC环境:可直接使用,无需校准
 * 2. 非Invariant-TSC环境:需要校准以获得准确的时间测量
 * 3. 处理核心迁移等复杂情况
 */
class HardwareTimer {
 public:
  /**
   * @brief 构造函数,自动检测TSC类型并进行校准
   * @param autoCalibrate 是否在初始化时自动校准(默认为true)
   */
  explicit HardwareTimer(bool autoCalibrate = true)
      : frequency_(0), isInvariant_(false), isCalibrated_(false) {
    // 检测TSC支持特性
    detectTSCSupport();

    if (isInvariant_) {
      // 对于invariant-TSC,我们可以使用估计的频率
      frequency_ = estimateFrequency();
    }

    if (autoCalibrate || !isInvariant_) {
      // 如果需要自动校准或者不是invariant-TSC,则进行校准
      calibrate();
    }
  }

  /**
   * @brief 构造函数,使用用户提供的已知频率
   * @param knownFrequency 已知的TSC频率(Hz)
   * @param isInvariantTSC TSC是否为invariant类型
   */
  explicit HardwareTimer(uint64_t knownFrequency, bool isInvariantTSC = true)
      : frequency_(knownFrequency),
        isInvariant_(isInvariantTSC),
        isCalibrated_(true) {
    // 用户提供频率,无需校准
    // 仍然检测TSC支持特性以获取其他信息
    detectTSCSupport();
  }

  /**
   * @brief 校准计时器以获取CPU频率
   * @details 使用std::chrono::high_resolution_clock进行校准,并处理核心迁移问题
   */
  inline void calibrate() {
    using namespace std::chrono;

    if (!hasTSC_) {
      // 如果不支持TSC,则无法校准
      return;
    }

    // 对于支持RDTSCP的系统,我们可以检测核心迁移
    if (hasRDTSCP_) {
      calibrateWithCoreMigrationDetection();
    } else {
      // 对于不支持RDTSCP的系统,使用基本校准方法
      calibrateBasic();
    }
  }

  /**
   * @brief 带核心迁移检测的校准方法
   */
  inline void calibrateWithCoreMigrationDetection() {
    using namespace std::chrono;

    const int calibrationTimeMs = 100;  // 校准时间(毫秒)
    const int numSamples = 5;           // 采样次数

    std::vector<uint64_t> frequencies(numSamples);

    for (int i = 0; i < numSamples; ++i) {
      // 等待下一个时间片开始,提高准确性
      std::this_thread::sleep_until(high_resolution_clock::now() +
                                    milliseconds(1));

      unsigned int startAux, endAux;
      auto startChrono = high_resolution_clock::now();
      uint64_t startTSC = rdtscp(&startAux);

      // 精确延时一段时间
      std::this_thread::sleep_for(milliseconds(calibrationTimeMs));

      uint64_t endTSC = rdtscp(&endAux);
      auto endChrono = high_resolution_clock::now();

      auto durationNano =
          duration_cast<nanoseconds>(endChrono - startChrono).count();

      // 计算频率
      frequencies[i] = static_cast<uint64_t>((endTSC - startTSC) *
                                             1000000000ULL / durationNano);

      // 如果检测到核心迁移,我们可能需要重新采样
      if (startAux != endAux) {
        // 核心迁移发生,这个样本可能不准确,我们重新采样
        --i;  // 重新进行这次采样
        continue;
      }
    }

    // 对采样结果进行排序并取中位数,以减少异常值的影响
    std::sort(frequencies.begin(), frequencies.end());
    frequency_ = frequencies[numSamples / 2];  // 取中位数
    isCalibrated_ = true;
  }

  /**
   * @brief 基本校准方法(不检测核心迁移)
   */
  inline void calibrateBasic() {
    using namespace std::chrono;

    // 使用high_resolution_clock进行校准
    const int calibrationTimeMs = 100;  // 校准时间(毫秒)

    // 等待下一个时间片开始,提高准确性
    std::this_thread::sleep_until(high_resolution_clock::now() +
                                  milliseconds(1));

    auto startChrono = high_resolution_clock::now();
    uint64_t startTSC = rdtsc();

    // 精确延时一段时间
    std::this_thread::sleep_for(milliseconds(calibrationTimeMs));

    uint64_t endTSC = rdtsc();
    auto endChrono = high_resolution_clock::now();

    auto durationNano =
        duration_cast<nanoseconds>(endChrono - startChrono).count();

    // 计算频率
    frequency_ = static_cast<uint64_t>((endTSC - startTSC) * 1000000000ULL /
                                       durationNano);
    isCalibrated_ = true;
  }

  /**
   * @brief 获取计时器频率(Hz)
   * @return 计时器频率
   */
  inline uint64_t frequency() const { return frequency_; }

  /**
   * @brief 检查TSC是否为invariant(恒定不变)
   * @return true 如果TSC是invariant的
   */
  inline bool isInvariant() const { return isInvariant_; }

  /**
   * @brief 检查是否支持RDTSCP指令
   * @return true 如果支持RDTSCP指令
   */
  inline bool hasRDTSCP() const { return hasRDTSCP_; }

  /**
   * @brief 检查是否支持TSC(基本)
   * @return true 如果支持TSC
   */
  inline bool hasTSC() const { return hasTSC_; }

  /**
   * @brief 检查是否已校准
   * @return true 如果已校准
   */
  inline bool isCalibrated() const { return isCalibrated_; }

  /**
   * @brief 读取当前TSC值和处理器ID
   * @param aux 输出参数,返回处理器ID信息
   * @return 当前TSC值
   */
  static inline uint64_t rdtscp(unsigned int* aux = nullptr) {
#ifdef _WIN32
    return __rdtscp(aux);
#else
    return __rdtscp(aux);
#endif
  }

  /**
   * @brief 读取当前TSC值(无处理器ID)
   * @return 当前TSC值
   */
  static inline uint64_t rdtsc() {
#ifdef _WIN32
    return __rdtsc();
#else
    return __rdtsc();
#endif
  }

  /**
   * @brief 获取两个时间戳之间的时间差(以秒为单位)
   * @param start 起始时间戳
   * @param end 结束时间戳
   * @return 时间差(秒)
   */
  inline double secondsBetween(uint64_t start, uint64_t end) const {
    return static_cast<double>(end - start) / frequency_;
  }

  /**
   * @brief 获取两个时间戳之间的时间差(以毫秒为单位)
   * @param start 起始时间戳
   * @param end 结束时间戳
   * @return 时间差(毫秒)
   */
  inline double millisecondsBetween(uint64_t start, uint64_t end) const {
    return static_cast<double>(end - start) * 1000.0 / frequency_;
  }

  /**
   * @brief 获取两个时间戳之间的时间差(以微秒为单位)
   * @param start 起始时间戳
   * @param end 结束时间戳
   * @return 时间差(微秒)
   */
  inline double microsecondsBetween(uint64_t start, uint64_t end) const {
    return static_cast<double>(end - start) * 1000000.0 / frequency_;
  }

  /**
   * @brief 获取两个时间戳之间的时间差(以纳秒为单位)
   * @param start 起始时间戳
   * @param end 结束时间戳
   * @return 时间差(纳秒)
   */
  inline double nanosecondsBetween(uint64_t start, uint64_t end) const {
    return static_cast<double>(end - start) * 1000000000.0 / frequency_;
  }

 private:
  /**
   * @brief 检测TSC支持特性
   */
  inline void detectTSCSupport() {
    // 检查基本的TSC支持 (CPUID.1.EDX.TSC[bit 4])
    bool hasBasicTSC = false;
#ifdef _WIN32
    int cpuInfo[4];
    __cpuid(cpuInfo, 1);
    hasBasicTSC = (cpuInfo[3] & (1 << 4)) != 0;

    // 检查RDTSCP支持 (CPUID.80000001H.EDX.RDTSCP[bit 27])
    __cpuid(cpuInfo, 0x80000001);
    hasRDTSCP_ = (cpuInfo[3] & (1 << 27)) != 0;

    // 检查Invariant TSC (CPUID.80000007H.EDX.InvariantTSC[bit 8])
    __cpuid(cpuInfo, 0x80000007);
    isInvariant_ = (cpuInfo[3] & (1 << 8)) != 0;
#else
    unsigned int eax, ebx, ecx, edx;
    if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
      hasBasicTSC = (edx & (1 << 4)) != 0;
    }

    // 检查RDTSCP支持 (CPUID.80000001H.EDX.RDTSCP[bit 27])
    if (__get_cpuid(0x80000001, &eax, &ebx, &ecx, &edx)) {
      hasRDTSCP_ = (edx & (1 << 27)) != 0;
    }

    // 检查Invariant TSC (CPUID.80000007H.EDX.InvariantTSC[bit 8])
    if (__get_cpuid(0x80000007, &eax, &ebx, &ecx, &edx)) {
      isInvariant_ = (edx & (1 << 8)) != 0;
    }
#endif

    hasTSC_ = hasBasicTSC;
  }

  /**
   * @brief 估算TSC频率(无需精确校准)
   * @return 估算的TSC频率
   */
  inline uint64_t estimateFrequency() {
    // 如果是invariant TSC,我们可以通过CPUID获取基础频率
#ifdef _WIN32
    int cpuInfo[4];
    __cpuid(cpuInfo, 0x16);  // CPU频率信息

    // EAX contains base frequency in MHz
    if (cpuInfo[0] > 0) {
      return (static_cast<uint64_t>(cpuInfo[0] & 0xFFFF) * 1000000ULL);
    }
#else
    unsigned int eax, ebx, ecx, edx;
    if (__get_cpuid(0x16, &eax, &ebx, &ecx, &edx)) {
      // EAX contains base frequency in MHz
      if (eax > 0) {
        return (static_cast<uint64_t>(eax & 0xFFFF) * 1000000ULL);
      }
    }
#endif

    // 如果无法通过CPUID获取频率,返回一个合理的默认值(例如3.0 GHz)
    // 在高性能计时器中,这是可以接受的近似值
    return 3000000000ULL;  // 3.0 GHz
  }

  uint64_t frequency_;  ///< TSC频率(Hz)
  bool isInvariant_;    ///< TSC是否为invariant
  bool hasRDTSCP_;      ///< 是否支持RDTSCP指令
  bool hasTSC_;         ///< 是否支持TSC
  bool isCalibrated_;   ///< 是否已完成校准
};

#endif  // HARDWARE_TIMER_H