通过


获取高分辨率时间戳

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 的处理器。 运行这些版本 Windows 的系统如果固件有缺陷,并且使用 TSC 作为 QPC 的基础,那么在不同核心上可能无法提供相同的 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 是高分辨率硬件计数器,可以通过非常低的延迟和开销访问(根据处理器类型,通常在 10 个或 100 个机器周期的范围内访问)。 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 的支持,该 API 支持从操作系统获取精确的时钟时间戳。 有关详细信息,请参阅 GetSystemTimePreciseAsFileTime。 在使用 Arm 处理器的 Windows RT 和 Windows 11 和 Windows 10 设备上,性能计数器基于专有平台计数器或 Arm 通用计时器提供的系统计数器(如果平台装备如此)。

获取时间戳指南

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

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

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

  • 在某些处理器上运行的先于 Windows Vista 的作系统可能会违反此一致性,其原因如下:

    • 硬件处理器具有非固定 TSC,BIOS 未正确指示此条件。
    • 使用的 TSC 同步算法不适合具有大量处理器的系统。
  • 比较从不同线程获取的性能计数器结果时,要注意差异在 ± 1 计时单位内的值可导致顺序不明确。 如果时间戳取自同一线程,则此± 1 个时钟周期不确定性不适用。 在此上下文中,术语刻度是指等于 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 寄存器或 RDTSC 或 RDTSCP 处理器指令。

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 实用工具
  • 检查与 TSC 特征相关的 CPUID 指令返回的值
  • 处理器制造商的文档

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

> 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 位整数和浮点(double)之间的转换可能会导致精度损失,因为浮点 mantissa 不能表示所有可能的整型值。
  • 64 位整数的乘法可能会导致整数溢出。

作为一般原则,尽可能延迟这些计算和转换,以避免增加引入的错误。

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

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

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

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

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

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

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

如果向函数传递有效参数,则不会在运行 Windows XP 或更高版本的任何系统上执行此作。

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

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

低级别硬件时钟特征

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

绝对时钟和差异时钟

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

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

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

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

时间间隔

计时器的分辨率等于周期。 解决方法确定能够区分任意两个时间戳,并将下限放在可测量的最小时间间隔上。 这有时称为时钟周期分辨率。

时间的数字测量引入了± 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.0000000320(320 纳秒)。 因此,每个时钟刻度表示 320 纳秒的经过。 在此计算机上无法测量小于 320 纳秒的时间间隔。

刻度间隔 = 1/(性能频率)

刻度间隔 = 1 / 3,125,000 = 320 ns

示例 2

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

ElapsedTime = Ticks * 刻度间隔

ElapsedTime = 5 * 320 ns = 1.6μs

从软件中访问滴答计数器需要时间(读取所需时间),而这种访问时间可能会降低测量时间的精度。 这是因为最小间隔时间(可以测量的最小时间间隔)是分辨率和访问时间中较大的那个。

精度 = MAX [分辨率,访问时间]

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

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

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

qpc 访问时间

如果访问时间大于分辨率,请不要尝试通过猜测来提高精度。 换句话说,假设时间戳是精确地在通话的中间、开头或末尾记录的,这是一个错误。

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

qpc 精度

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

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

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

 

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

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

计时器的准确性是指符合真实值或标准值的程度。 这主要取决于晶体振荡器提供指定频率脉冲的能力。 如果振荡频率过高,时钟将“快速运行”,测量间隔的显示时间将比实际长:如果频率太低,时钟将“运行缓慢”,测量的间隔将比实际间隔短。

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

晶体的振荡频率在制造过程中设置,由制造商根据指定的频率加上或减去以“每百万部分”(ppm)表示的制造容差(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 小时 ± 36 毫秒
1 天 ± 0.86 秒
1 周 ± 6.08 秒

 

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

在个人电脑和服务器中使用的晶体振荡器通常以频率容差为±30到50百万分之一制造,但在极少数情况下,晶体偏差可高达500百万分之一。 虽然具有更严格的频率偏移容差的晶体可用,但它们更昂贵,因此在大多数计算机上不使用。

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

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

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

示例 1

假设您使用一个分辨率为 1 微秒、最大频率偏移误差为 ±50 ppm 的 1 MHz 振荡器来执行时间间隔测量。 现在,假设偏移量正好是 +50 ppm。 这意味着实际频率为 1,000,050 Hz。 如果我们测量的时间间隔为 24 小时,我们的测量将短缺 4.3 秒(测得的时间为 23:59:55.700000,实际时间为 24: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.7000000000 测量值 对比 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 中清除不变的 TSC 功能位。
  • 在多处理器或多核系统上,某些处理器和系统无法将每个核心上的时钟同步到相同的值。 (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 起,Windows 系统中一直支持 HPET。Windows 7 和 Windows 8 的硬件徽标认证要求硬件平台必须支持 HPET。

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

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

周期计数器 (Arm)

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