延迟测量(时钟周期)¶
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. 性能检测方式的工作机制及实现细节¶
rdtsc函数:利用汇编指令 rdtsc 从 TSC 寄存器中读取值,将低 32 位和高 32 位合并成一个 64 位的 uint64_t 类型值,代表经过的 CPU 时钟周期。这是整个性能检测的基础,用于获取精确的 CPU 时钟周期数。START_MEASURE宏:在需要开始测量延迟的地方使用该宏,它会调用 rdtsc 函数获取当前的 CPU 时钟周期数,并创建一个指定名称(TAG)的常量变量存储该值,用于后续计算延迟。END_MEASURE宏:在需要结束测量延迟的地方使用该宏,它再次调用 rdtsc 函数获取当前的 CPU 时钟周期数,计算与开始测量时存储的值的差值,得到延迟的 CPU 时钟周期数,并通过日志记录器(LOGGER)记录当前时间字符串和延迟值。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