获取高分辨率时间戳

Windows 提供可用于获取高分辨率时间戳或测量时间间隔的 API。 本机代码的主要 API 是 QueryPerformanceCounter (QPC) 。 对于设备驱动程序,内核模式 API 为 KeQueryPerformanceCounter。 对于托管代码, System.Diagnostics.Stopwatch 类使用 QPC 作为其精确时间基础。

QPC 独立于任何外部时间引用,并且不会同步到任何外部时间引用。 若要检索可同步到外部时间参考的时间戳,例如协调世界时 (UTC) 以用于高分辨率一天中的时间度量,请使用 GetSystemTimePreciseAsFileTime

时间戳和时间间隔度量是计算机和网络性能度量不可或缺的一部分。 这些性能度量操作包括计算响应时间、吞吐量和延迟,以及分析代码执行。 其中每个操作都涉及测量在时间间隔内发生的活动,该时间间隔由开始事件和结束事件定义,该事件可以独立于任何外部时间引用。

QPC 通常是用于时间戳事件和测量同一系统或虚拟机上发生的较小时间间隔的最佳方法。 如果要跨多台计算机标记事件,请考虑使用 GetSystemTimePreciseAsFileTime ,前提是每台计算机都参与时间同步方案,例如网络时间协议 (NTP) 。 QPC 可帮助你避免使用其他时间测量方法时可能会遇到的困难,例如直接读取处理器的时间戳计数器 (TSC) 。

Windows 版本中的 QPC 支持

QPC 是在 Windows 2000 和 Windows XP 中引入的,它已演变为利用硬件平台和处理器的改进。 下面我们介绍不同 Windows 版本上的 QPC 的特征,以帮助你维护在这些 Windows 版本上运行的软件。

Windows XP 和 Windows 2000

QPC 在 Windows XP 和 Windows 2000 上可用,适用于大多数系统。 但是,某些硬件系统的 BIOS 未正确指示硬件 CPU 特征 (非固定 TSC) ,并且某些多核或多处理器系统使用无法跨内核同步的具有 TSC 的处理器。 如果系统使用 TSC 作为 QPC 的基础,则运行这些版本的 Windows 的固件存在缺陷的系统可能无法在不同的核心上提供相同的 QPC 读取。

Windows Vista 和 Windows Server 2008

Windows Vista 和 Windows Server 2008 随附的所有计算机都使用平台计数器 (高精度事件计时器 (HPET) ) 或 ACPI 电源管理计时器 (PM 计时器) 作为 QPC 的基础。 此类平台计时器的访问延迟高于 TSC,并在多个处理器之间共享。 如果从多个处理器同时调用 QPC ,这将限制其可伸缩性。

Windows 7 和 Windows Server 2008 R2

大多数 Windows 7 和 Windows Server 2008 R2 计算机都具有具有恒定速率 TSC 的处理器,并使用这些计数器作为 QPC 的基础。 TSC 是每个处理器的高分辨率硬件计数器,可以根据处理器类型) ,以非常低的延迟和开销 (访问。 Windows 7 和 Windows Server 2008 R2 使用 TSC 作为单时钟域系统上 QPC 的基础,其中操作系统 (或虚拟机监控程序) 能够在系统初始化期间跨所有处理器紧密同步各个 TSC。 在此类系统上,与使用平台计数器的系统相比,读取性能计数器的成本要低很多。 此外,并发调用不会增加开销,用户模式查询通常绕过系统调用,这进一步减少了开销。 在 TSC 不适合计时的系统上,Windows 会自动选择一个平台计数器, (HPET 计时器或 ACPI PM 计时器) 作为 QPC 的基础。

Windows 8、Windows 8.1、Windows Server 2012 和 Windows Server 2012 R2

Windows 8、Windows 8.1、Windows Server 2012和Windows Server 2012 R2 使用 TSC 作为性能计数器的基础。 TSC 同步算法已得到显著改进,以更好地适应具有多个处理器的大型系统。 此外,还添加了对新的精确时间 API 的支持,以便从操作系统获取精确的时钟时间戳。 有关详细信息,请参阅 GetSystemTimePreciseAsFileTime。 在使用 Arm 处理器的Windows RT和Windows 11和Windows 10设备上,性能计数器基于专有平台计数器,或者基于 Arm 通用计时器提供的系统计数器(如果平台如此配置)。

获取时间戳的指南

Windows 已经并将继续投资提供可靠且高效的性能计数器。 如果需要分辨率为 1 微秒或更高的时间戳,并且不需要将时间戳同步到外部时间引用,请选择 QueryPerformanceCounterKeQueryPerformanceCounterKeQueryInterruptTimePrecise。 如果需要分辨率为 1 微秒或更高的 UTC 同步时间戳,请选择 GetSystemTimePreciseAsFileTimeKeQuerySystemTimePrecise

在无法将 TSC 寄存器用作 QPC 基础的相对较少的平台上,例如,出于 硬件计时器信息中所述的原因,获取高分辨率时间戳的成本可能明显高于获取分辨率较低的时间戳的成本。 如果 10 到 16 毫秒的分辨率足够,可以使用 GetTickCount64QueryInterruptTimeQueryUnbiasedInterruptTimeKeQueryInterruptTimeKeQueryUnbiasedInterruptTime 获取未同步到外部时间引用的时间戳。 对于 UTC 同步时间戳,请使用 GetSystemTimeAsFileTimeKeQuerySystemTime。 如果需要更高的分辨率,可以改用 QueryInterruptTimePreciseQueryUnbiasedInterruptTimePreciseKeQueryInterruptTimePrecise 来获取时间戳。

通常,性能计数器结果在多核和多处理器系统中的所有处理器之间是一致的,即使在不同的线程或进程中测量时也是如此。 下面是此规则的一些例外情况:

  • 在某些处理器上运行的 Windows Vista 之前的操作系统可能会由于以下原因之一而违反此一致性:

    • 硬件处理器具有非固定 TSC,BIOS 未正确指示此条件。
    • 使用的 TSC 同步算法不适用于具有大量处理器的系统。
  • 比较从不同线程获取的性能计数器结果时,请考虑± 1 个刻度不同的值,其顺序不明确。 如果时间戳取自同一线程,则此± 1 刻度不确定性不适用。 在此上下文中,术语 tick 是指等于 1 ÷ (从 QueryPerformanceFrequency) 获取的性能计数器的频率的时间段。

在具有硬件中未同步的多时钟域的大型服务器系统上使用性能计数器时,Windows 确定 TSC 不能用于计时目的,并选择平台计数器作为 QPC 的基础。 虽然此方案仍生成可靠的时间戳,但访问延迟和可伸缩性会受到不利影响。 因此,如前面的使用指南中所述,仅在需要此类解析时使用提供 1 微秒或更好分辨率的 API。 TSC 用作多时钟域系统上 QPC 的基础,这些系统包括所有处理器时钟域的硬件同步,因为这样可以有效地使它们充当单个时钟域系统。

性能计数器的频率在系统启动时固定,并且在所有处理器中保持一致,因此只需在应用程序初始化时从 QueryPerformanceFrequency 查询频率,然后缓存结果。

虚拟化

性能计数器应在正确实现的虚拟机监控程序上运行的所有来宾虚拟机上可靠工作。 但是,符合虚拟机监控程序版本 1.0 接口并显示参考时间启发的虚拟机监控程序可以提供大幅降低的开销。 有关虚拟机监控程序接口和启发的详细信息,请参阅 虚拟机监控程序规范

直接 TSC 使用情况

我们强烈建议不要使用 RDTSCRDTSCP 处理器指令直接查询 TSC,因为在某些版本的 Windows 上、虚拟机的实时迁移以及没有固定或紧密同步 TSC 的硬件系统上,你不会获得可靠的结果。 相反,我们建议使用 QPC 来利用它提供的抽象性、一致性和可移植性。

获取时间戳的示例

这些部分中的各种代码示例演示如何获取时间戳。

在本机代码中使用 QPC

此示例演示如何在 C 和 C++ 本机代码中使用 QPC

LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
LARGE_INTEGER Frequency;

QueryPerformanceFrequency(&Frequency); 
QueryPerformanceCounter(&StartingTime);

// Activity to be timed

QueryPerformanceCounter(&EndingTime);
ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;


//
// We now have the elapsed number of ticks, along with the
// number of ticks-per-second. We use these values
// to convert to the number of elapsed microseconds.
// To guard against loss-of-precision, we convert
// to microseconds *before* dividing by ticks-per-second.
//

ElapsedMicroseconds.QuadPart *= 1000000;
ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;

从托管代码获取高分辨率时间戳

此示例演示如何使用托管代码 System.Diagnostics.Stopwatch 类。

using System.Diagnostics;

long StartingTime = Stopwatch.GetTimestamp();

// Activity to be timed

long EndingTime  = Stopwatch.GetTimestamp();
long ElapsedTime = EndingTime - StartingTime;

double ElapsedSeconds = ElapsedTime * (1.0 / Stopwatch.Frequency);

System.Diagnostics.Stopwatch 类还提供了几种方便的方法来执行时间间隔测量。

从内核模式使用 QPC

Windows 内核通过 KeQueryPerformanceCounter 提供对性能计数器的内核模式访问,可从中获取性能计数器和性能频率。 KeQueryPerformanceCounter 仅适用于内核模式,并且提供给设备驱动程序和其他内核模式组件的编写者。

此示例演示如何在 C 和 C++ 内核模式下使用 KeQueryPerformanceCounter

LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
LARGE_INTEGER Frequency;

StartingTime = KeQueryPerformanceCounter(&Frequency);

// Activity to be timed

EndingTime = KeQueryPerformanceCounter(NULL);
ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
ElapsedMicroseconds.QuadPart *= 1000000;
ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;

有关 QPC 和 TSC 的一般常见问题解答

下面是有关 QPC 和 TSC 的常见问题的解答。

QueryPerformanceCounter () 是否与 Win32 GetTickCount () 或 GetTickCount64 () 函数相同?

否。 GetTickCountGetTickCount64QPC 无关。 GetTickCountGetTickCount64 返回自系统启动以来的毫秒数。

我应该使用 QPC 还是直接调用 RDTSC/RDTSCP 指令?

为了避免错误和可移植性问题,强烈建议使用 QPC ,而不是使用 TSC 寄存器或 RDTSCRDTSCP 处理器指令。

QPC 与外部时间纪元的关系是什么?是否可以将其同步到外部纪元(如 UTC)?

QPC 基于无法同步到外部时间引用(如 UTC)的硬件计数器。 若要获取可同步到外部 UTC 引用的精确时间时间戳,请使用 GetSystemTimePreciseAsFileTime

QPC 是否受管理员进行的夏令时、闰秒、时区或系统时间更改的影响?

否。 QPC 完全独立于系统时间和 UTC。

QPC 准确度是否受电源管理或 Turbo Boost 技术导致的处理器频率更改的影响?

否。 如果处理器具有固定 TSC,则 QPC 不受这些更改的影响。 如果处理器没有固定 TSC,QPC 将还原到不受处理器频率更改或 Turbo Boost 技术影响的平台硬件计时器。

QPC 是否可靠地适用于多处理器系统、多核系统和超线程系统?

是的。

如何实现确定并验证 QPC 在我的计算机上是否正常工作?

无需执行此类检查。

哪些处理器具有非固定 TSC?如果系统具有非固定 TSC,如何检查?

无需自行执行此检查。 Windows 操作系统在系统初始化时执行多次检查,以确定 TSC 是否适合作为 QPC 的基础。 但是,出于参考目的,可以使用以下方法之一确定处理器是否具有固定 TSC:

  • Windows Sysinternals 中的Coreinfo.exe实用工具
  • 检查 CPUID 指令返回的值,这些值与 TSC 特征相关
  • 处理器制造商的文档

下面显示了 Windows Sysinternals Coreinfo.exe 实用工具 (www.sysinternals.com) 提供的 TSC-INVARIANT 信息。 星号表示“True”。

> Coreinfo.exe 

Coreinfo v3.2 - Dump information on system CPU and memory topology
Copyright (C) 2008-2012 Mark Russinovich
Sysinternals - www.sysinternals.com

 <unrelated text removed>

RDTSCP          * Supports RDTSCP instruction
TSC             * Supports RDTSC instruction
TSC-DEADLINE    - Local APIC supports one-shot deadline timer
TSC-INVARIANT   * TSC runs at constant rate

QPC 是否在Windows RT硬件平台上可靠地工作?

是的。

QPC 多久滚动更新一次?

从最近的系统启动开始不少于 100 年,并且根据使用的基础硬件计时器,可能会更长的时间。 对于大多数应用程序,滚动更新不是问题。

调用 QPC 的计算成本是多少?

QPC 的计算调用成本主要由基础硬件平台决定。 如果使用 TSC 寄存器作为 QPC 的基础,则计算成本主要取决于处理器处理 RDTSC 指令所需的时间。 此时间范围从 10 个 CPU 周期到几百个 CPU 周期,具体取决于所使用的处理器。 如果无法使用 TSC,系统会选择其他硬件时间依据。 由于这些时间基位于主板 (例如,在 PCI 南桥或 PCH) 上,因此每个呼叫的计算成本高于 TSC,并且通常接近 0.8 - 1.0 微秒,具体取决于处理器速度和其他硬件因素。 此成本由访问主板上的硬件设备所需的时间决定。

QPC 是否需要内核转换 (系统调用) ?

如果系统可以使用 TSC 寄存器作为 QPC 的基础,则不需要内核转换。 如果系统必须使用其他时间基,例如 HPET 或 PM 计时器,则需要系统调用。

性能计数器是否单调 (非递减) ?

是的。 QPC 不会后退。

性能计数器能否用于及时对事件进行排序?

是的。 但是,在比较从不同线程获取的性能计数器结果时,± 1 刻度差异的值顺序不明确,就好像它们具有相同的时间戳一样。

性能计数器的准确度如何?

答案取决于各种因素。 有关详细信息,请参阅 低级别硬件时钟特征

有关使用 QPC 和 TSC 编程的常见问题解答

下面是有关使用 QPC 和 TSC 编程的常见问题的解答。

我需要将 QPC 输出转换为毫秒。如何避免转换为双精度或浮点数的精度损失?

在整数性能计数器上执行计算时,需要注意以下几点:

  • 整数除法将失去余数。 在某些情况下,这可能会导致精度损失。
  • 64 位整数与浮点 (双) 之间的转换可能会导致精度损失,因为浮点 mantissa 无法表示所有可能的整数值。
  • 64 位整数的乘法可能会导致整数溢出。

作为一般原则,应尽可能长时间地延迟这些计算和转换,以避免使引入的错误复杂化。

如何将 QPC 转换为 100 纳秒时钟周期,以便将其添加到 FILETIME?

文件时间是一个 64 位值,表示自 1601 年 1 月 1 日凌晨 12:00(协调世界时 (UTC) )以来经过的 100 纳秒间隔数。 文件时间由返回当天时间的 Win32 API 调用使用,例如 GetSystemTimeAsFileTimeGetSystemTimePreciseAsFileTime。 相比之下, QueryPerformanceCounter 返回的值表示时间(单位为 1/ (从 QueryPerformanceFrequency) 获取的性能计数器的频率)。 两者之间的转换需要计算 QPC 间隔和 100 纳秒间隔的比率。 请小心避免丢失精度,因为值可能较小, (0.0000001 / 0.000000340) 。

为什么从 QPC 返回的时间戳是有符号整数?

涉及 QPC 时间戳的计算可能涉及减法。 通过使用带符号值,可以处理可能产生负值的计算。

如何从托管代码获取高分辨率时间戳?

System.Diagnostics.Stopwatch 类调用 Stopwatch.GetTimeStamp 方法。 有关如何使用 Stopwatch.GetTimeStamp 的示例,请参阅 从托管代码获取高分辨率时间戳

在什么情况下,QueryPerformanceFrequency 返回 FALSE,或 QueryPerformanceCounter 返回零?

这不会发生在运行 Windows XP 或更高版本的任何系统上。

是否需要将线程相关性设置为单个核心才能使用 QPC?

否。 有关详细信息,请参阅 获取时间戳的指南。 此方案既没有必要也不必要。 如果多个线程在调用 QueryPerformanceCounter 时将其相关性设置为同一个核心,则执行此方案可能会对应用程序的性能产生负面影响,方法是将处理限制为一个核心,或者在单个核心上造成瓶颈。

低级别硬件时钟特征

这些部分显示了低级别硬件时钟特征。

绝对时钟和差异时钟

绝对时钟提供准确的一天中的时间读数。 它们通常基于协调世界时 (UTC) ,因此其准确性在一定程度上取决于它们与外部时间参考的同步程度。 差异时钟测量时间间隔,通常不基于外部时间纪元。 QPC 是一个差异时钟,不会同步到外部时间纪元或引用。 使用 QPC 进行时间间隔度量时,通常比使用派生自绝对时钟的时间戳获得更好的准确度。 这是因为同步绝对时钟的时间的过程可能会引入相位和频率偏移,从而增加短期时间间隔测量的不确定性。

分辨率、精度、准确性和稳定性

QPC 使用硬件计数器作为其基础。 硬件计时器由三个部分组成:时钟周期生成器、计数时钟周期的计数器和检索计数器值的方法。 这三个分量的特征决定了 QPC 的分辨率、精度、准确性和稳定性。

如果硬件生成器以恒定速率提供时钟周期,则只需计算这些时钟周期即可测量时间间隔。 时钟周期的生成速率称为频率,以Hz (Hz) 表示。 频率的倒数称为周期或刻度间隔,以适当的国际单位制 (SI) 时间单位 (表示,例如秒、毫秒、微秒或纳秒) 。

时间间隔

计时器的分辨率等于句点。 分辨率确定区分任意两个时间戳的能力,并在可以测量的最小时间间隔上设置下限。 这有时称为时钟周期解析。

数字时间测量引入了± 1 滴答的测量不确定性,因为数字计数器在离散步骤中前进,而时间在不断推进。 这种不确定性称为量化错误。 对于典型的时间间隔度量,通常可以忽略此效果,因为量化误差比测量的时间间隔小得多。

数字时间测量

但是,如果测量的周期较小,并且接近计时器的分辨率,则需要考虑此量化错误。 引入的错误大小为一个时钟周期。

以下两个关系图演示了使用分辨率为 1 时间单位的计时器± 1 刻度不确定性的影响。

时钟周期不确定性

QueryPerformanceFrequency 返回 QPC 的频率,并且周期和分辨率等于此值的倒数。 QueryPerformanceFrequency 返回的性能计数器频率在系统初始化期间确定,在系统运行时不会更改。

注意

通常,QueryPerformanceFrequency 不会返回硬件时钟周期生成器的实际频率。 例如,在某些较旧版本的 Windows 中,QueryPerformanceFrequency 返回除以 1024 的 TSC 频率;在实现虚拟机监控程序版本 1.0 接口虚拟机监控程序 (或始终在某些较新版本的 Windows) 下运行时,性能计数器频率固定为 10 MHz。 因此,不要假定 QueryPerformanceFrequency 将返回派生自硬件频率的值。

 

QueryPerformanceCounter 读取性能计数器并返回自 Windows 操作系统启动以来发生的时钟周期总数,包括计算机处于睡眠状态(如待机、休眠或连接待机)的时间。

这些示例演示如何计算时钟周期间隔和分辨率,以及如何将刻度计数转换为时间值。

示例 1

QueryPerformanceFrequency 在特定计算机上返回值 3,125,000。 此计算机上的 QPC 测量的刻度间隔和分辨率是多少? 刻度间隔(或周期)是 3,125,000 的倒数,即 0.000000320 (320 纳秒) 。 因此,每个刻度表示 320 纳秒的传递。 在此计算机上无法测量小于 320 纳秒的时间间隔。

时钟周期间隔 = 1/ (性能频率)

时钟周期间隔 = 1/3,125,000 = 320 ns

示例 2

在与前面的示例相同的计算机上,连续两次调用 QPC 返回的值之差为 5。 两个调用之间已经过去了多少时间? 5 个刻度乘以 320 纳秒产生 1.6 微秒。

ElapsedTime = Ticks * Tick Interval

ElapsedTime = 5 * 320 ns = 1.6μs

访问 (从软件读取) 计时周期计数器需要一些时间,并且此访问时间可能会降低时间度量的精度。 这是因为最小间隔时间 (可以测量的最小时间间隔) 是分辨率和访问时间的较大。

Precision = MAX [ Resolution, AccessTime]

例如,假设硬件计时器的分辨率为 100 纳秒,访问时间为 800 纳秒。 如果使用平台计时器而不是 TSC 寄存器作为 QPC 的基础,则可能发生这种情况。 因此,精度为 800 纳秒,而不是 100 纳秒,如此计算所示。

精度 = MAX [800 ns,100 ns] = 800 ns

这两个数字描绘了这种效果。

qpc 访问时间

如果访问时间大于分辨率,请不要尝试通过猜测来提高精度。 换句话说,假定时间戳恰好在调用的中间、调用的开始或结束时间是错误的。

相比之下,请考虑以下示例中的 QPC 访问时间仅为 20 纳秒,硬件时钟分辨率为 100 纳秒。 如果使用 TSC 寄存器作为 QPC 的基础,则可能发生这种情况。 此处的精度受时钟分辨率的限制。

qpc 精度

在实践中,可以找到读取计数器所需的时间大于或小于分辨率的时间源。 在任一情况下,两者中的精度都较大。

此表提供有关各种时钟的近似分辨率、访问时间和精度的信息。 请注意,某些值因处理器、硬件平台和处理器速度而异。

时钟源 名义时钟频率 时钟分辨率 访问时间 (典型) Precision
电脑 RTC 64 Hz 15.625 毫秒 空值 空值
使用 TSC 和 3 GHz 处理器时钟查询性能计数器 3 MHz 333 纳秒 30 纳秒 333 纳秒
具有 3 GHz 周期时间的系统上的 RDTSC 计算机指令 3 GHz 333 皮秒 30 纳秒 30 纳秒

 

由于 QPC 使用硬件计数器,因此了解硬件计数器的一些基本特征后,即可了解 QPC 的功能和限制。

最常用的硬件时钟周期生成器是晶体振荡器。 晶体是一小块石英或其他陶瓷材料,具有压电特性,提供廉价的频率参考,具有出色的稳定性和准确性。 此频率用于生成时钟计数的计时周期。

计时器的准确性是指与 true 或标准值的一致性程度。 这主要取决于晶体振子以指定频率提供滴答声的能力。 如果振荡频率过高,时钟将“快速运行”,测量的间隔看起来比实际更长:如果频率太低,时钟将“运行缓慢”,测量的间隔看起来比实际时间短。

对于持续时间较短的典型时间间隔度量 (例如响应时间度量、网络延迟度量等) ,硬件振子的准确度通常已足够。 但是,对于某些测量,振荡频率精度变得很重要,尤其是对于较长时间间隔或想要比较不同计算机上的度量值时。 本部分的其余部分探讨振荡精度的影响。

晶体的振荡频率在制造过程中设置,由制造商根据指定频率加减以“百万分之几” (ppm) (称为最大频率偏移量)表示的制造容差来指定。 指定频率为 1,000,000 Hz 且最大频率偏移量为 ± 10 ppm 的晶体,如果其实际频率介于 999,990 Hz 到 1,000,010 Hz 之间,则其实际频率在规范范围内。

通过将每百万个短语部分替换为微秒/秒,我们可以将此频率偏移误差应用于时间间隔度量。 偏移量为 + 10 ppm 的振子的误差为每秒 10 微秒。 因此,在测量 1 秒间隔时,它会快速运行,并将 1 秒间隔测量为 0.999990 秒。

一个方便的参考是,100 ppm 的频率误差会导致 24 小时后 8.64 秒的错误。 此表显示了由于累积误差在较长时间间隔内造成的测量不确定性。

时间间隔持续时间 +/- 10 PPM 频率容差累积误差导致的测量不确定性
1 微秒 ± 10 皮秒 (10-12)
1 毫秒 ± 10 纳秒 (10-9)
1 秒 ± 10 微秒
1 小时 ± 60 微秒
1 天 ± 0.86 秒
1 周 ± 6.08 秒

 

上表显示,对于较小的时间间隔,通常可以忽略频率偏移误差。 但是,在较长的时间间隔内,即使频率偏移较小,也可能导致较大的测量不确定性。

在个人电脑和服务器中使用的晶体振荡器通常以± 30 到 500 万分之一的频率容差制造,而且很少,晶体的偏差高达 500 ppm。 尽管提供频率偏移容差要小得多的晶体,但它们更昂贵,因此在大多数计算机中不使用。

为了减少此频率偏移误差的不利影响,最新版本的 Windows(尤其是Windows 8)使用多个硬件计时器来检测频率偏移并尽可能对其进行补偿。 此校准过程在 Windows 启动时执行。

如以下示例所示,硬件时钟的频率偏移误差会影响可实现的准确度,而时钟的分辨率可能不那么重要。

频率偏移误差影响可实现的准确性

示例 1

假设使用分辨率为 1 微秒的 1 MHz 振动器执行时间间隔测量,最大频率偏移误差为 ±50 ppm。 现在,假设偏移量正好为 +50 ppm。 这意味着实际频率为 1,000,050 Hz。 如果我们测量的时间间隔为 24 小时,则测量时间太短 4.3 秒 (23:59:55.7000000,而实际) 为 24:00:00.00.000000 秒。

一天中的秒数 = 86400

频率偏移误差 = 50 ppm = 0.00005

86,400 秒 * 0.00005 = 4.3 秒

示例 2

假设处理器 TSC 时钟由晶体振动器控制,其指定频率为 3 GHz。 这意味着分辨率为1/3,000,000,000或约333皮秒。 假设用于控制处理器时钟的晶体的频率容差为 ±50 ppm,实际上为 +50 ppm。 尽管分辨率令人印象深刻,但 24 小时的时间间隔测量仍然太短 4.3 秒。 (23:59:55.70000000000,实际) 为 24:00:00.0000000000。

一天中的秒数 = 86400

频率偏移误差 = 50 ppm = 0.00005

86,400 秒 * 0.00005 = 4.3 秒

这表明高分辨率 TSC 时钟不一定比较低分辨率的时钟提供更准确的测量。

示例 3

请考虑使用两台不同的计算机来测量相同的 24 小时时间间隔。 两台计算机都具有最大频率偏移量为 ± 50 ppm 的振动。 这两个系统上同一时间间隔的测量可以相距多远? 与前面的示例一样,± 50 ppm 在 24 小时后产生最大误差± 4.3 秒。 如果一个系统运行速度快 4.3 秒,另一个系统运行速度慢 4.3 秒,则 24 小时后的最大误差可能是 8.6 秒。

一天中的秒数 = 86400

频率偏移误差 = ±50 ppm = ±0.00005

± (86,400 秒 * 0.00005) = ±4.3 秒

两个系统之间的最大偏移量 = 8.6 秒

总之,在测量较长的时间间隔以及比较不同系统之间的度量值时,频率偏移误差变得越来越重要。

计时器的稳定性描述滴答频率是否随时间而变化,例如,温度变化的结果。 用作计算机上滴答生成器的硅晶体将随着温度的函数而表现出微小的频率变化。 与常见温度范围的频率偏移误差相比,热偏移引起的误差通常较小。 但是,便携式设备或受较大温度波动影响的设备的软件设计人员可能需要考虑这种影响。

硬件计时器信息

TSC 寄存器 (x86 和 x64)

所有现代 Intel 和 AMD 处理器都包含一个 TSC 寄存器,该寄存器是一个 64 位寄存器,该寄存器以高速率增加,通常等于处理器时钟。 可以通过 RDTSCRDTSCP 计算机指令读取此计数器的值,从而提供非常低的访问时间和计算成本(以几十或数百个计算机周期的顺序,具体取决于处理器)。

尽管 TSC 寄存器似乎是一种理想的时间戳机制,但在以下情况下,它无法可靠地运行以实现计时:

  • 并非所有处理器都具有可用 TSC 寄存器,因此在软件中使用 TSC 寄存器会直接产生可移植性问题。 在这种情况下, (Windows 将为 QPC 选择备用时间源,从而避免可移植性问题。)
  • 某些处理器可能会改变 TSC 时钟的频率或停止 TSC 寄存器的提升,这使得 TSC 不适合在这些处理器上用于计时目的。 这些处理器据说具有非固定 TSC 寄存器。 (Windows 会自动检测到此问题,并为 QPC) 选择备用时间源。
  • 即使虚拟化主机具有可用 TSC,当目标虚拟化主机没有或利用硬件辅助 TSC 缩放时,实时迁移正在运行的虚拟机也可能导致 TSC 频率的变化,而来宾可以看到。 (如果来宾可以进行这种类型的实时迁移,则虚拟机监控程序应清除 CPUID.)
  • 在多处理器或多核系统上,某些处理器和系统无法将每个内核上的时钟同步到相同的值。 (Windows 会自动检测到此问题,并为 QPC) 选择备用时间源。
  • 在某些大型多处理器系统上,即使处理器具有固定 TSC,也可能无法将处理器时钟同步到相同的值。 (Windows 会自动检测到此问题,并为 QPC) 选择备用时间源。
  • 某些处理器将无序执行指令。 当使用 RDTSC 对指令序列进行计时时,这可能会导致错误的周期计数,因为 RDTSC 指令的执行时间可能与程序中指定的时间不同。 为了响应此问题,已在某些处理器上引入了 RDTSCP 指令。

与其他计时器一样,TSC 基于一个事先不知道其确切频率且具有频率偏移误差的晶体晶体。 因此,在使用它之前,必须使用另一个计时参考对其进行校准。

在系统初始化期间,Windows 会检查 TSC 是否适合计时目的,并执行必要的频率校准和核心同步。

pm 时钟 (x86 和 x64)

ACPI 计时器(也称为 PM 时钟)已添加到系统体系结构中,以提供独立于处理器速度的可靠时间戳。 由于这是此计时器的单一目标,因此它在单个时钟周期中提供时间戳,但不提供任何其他功能。

HPET 计时器 (x86 和 x64)

高精度事件计时器 (HPET) 由 Intel 和 Microsoft 联合开发,以满足多媒体和其他时间敏感型应用程序的计时要求。 与 TSC(按处理器资源)不同,HPET 是平台范围的共享资源,尽管一个系统可能具有多个 HPET。 自 Windows Vista 起,HPET 支持一直在 Windows 中,Windows 7 和 Windows 8 硬件徽标认证需要硬件平台中的 HPET 支持。

通用计时器系统计数器 (Arm)

基于 Arm 的平台没有 TSC、HPET 或 PM 时钟,因为基于 Intel 或 AMD 的平台上有。 相反,Arm 处理器提供泛型计时器 (有时称为泛型间隔计时器,或 GIT) ,其中包含系统计数器寄存器 (例如,CNTVCT_EL0) 。 通用计时器系统计数器是固定频率的平台范围时间源。 它在启动时从零开始,以较高的速度增加。 在 Armv8.6 或更高版本中,这定义为恰好 1 GHz,但应通过读取由早期启动固件设置的时钟频率寄存器来确定。 有关详细信息,请参阅 DDI 0487) (“AArch64 状态中的通用计时器”一章。

循环计数器 (Arm)

基于 Arm 的平台提供性能监视器周期计数器寄存器 (例如,PMCCNTR_EL0) 。 此计数器对处理器时钟周期进行计数。 它是非固定的,其单位可能与实时不相关。 不建议使用此寄存器来获取时间戳。