gettimeofday()是否保证具有微秒级的分辨率?


97

我正在将一个最初为Win32 API编写的游戏移植到Linux(很好,是将Win32端口的OS X端口移植到Linux)。

QueryPerformanceCounter自程序启动以来,我已经通过提供uSeconds来实现:

BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
    gettimeofday(&currentTimeVal, NULL);
    performanceCount->QuadPart = (currentTimeVal.tv_sec - startTimeVal.tv_sec);
    performanceCount->QuadPart *= (1000 * 1000);
    performanceCount->QuadPart += (currentTimeVal.tv_usec - startTimeVal.tv_usec);

    return true;
}

再加上QueryPerformanceFrequency()恒定的1000000作为频率,可以在我的机器上很好地工作,并为我提供了一个uSeconds自程序启动以来包含的64位变量。

是便携式的吗?我不想发现以某种方式或类似方式编译内核时,它的工作原理有所不同。我可以将其移植到Linux以外的其他软件上,这很好。

Answers:


57

也许。但是你有更大的问题。gettimeofday()如果系统上的某些进程更改了计时器(例如ntpd),则可能导致错误的计时。但是,在“普通” Linux上,我相信的分辨率gettimeofday()为10us。因此,它可以根据系统上运行的进程向前和向后跳转,并可以跳转。有效地回答了您的问题。

您应该查看clock_gettime(CLOCK_MONOTONIC)时间间隔。由于诸如多核系统和外部时钟设置之类的问题,它遇到了一些更少的问题。

另外,查看该clock_getres()功能。


1
clock_gettime仅在最新的Linux上存在。其他系统只有gettimeofday()
vitaly.v.ch 09/12 / 18,16

3
@ vitaly.v.ch是POSIX,所以它不仅是Linux,而且是'newist'吗?甚至像Red Hat Enterprise Linux这样的“企业”发行版都基于2.6.18,它具有clock_gettime,所以不是,不是很新。.(RHEL中的联机手册日期为2004年3月12日,因此已经存在了一段时间),除非您谈论真正的老内核WTF是什么意思?
Spudd86 2010年

clock_gettime已包含在2001年的POSIX中。据我所知,目前clock_gettime()在Linux 2.6和qnx中实现。但是linux 2.4当前已在许多生产系统中使用。
vitaly.v.ch 2010年

它是在2001年推出,但2008年不是强制性的,直到POSIX
R.,GitHub上停止帮助ICE

2
从Linux常见问题解答中获取关于lock_gettime的信息(请参阅David Schlosnagle的答案):“ CLOCK_MONOTONIC ...是NTP通过adjtimex()进行频率调整的。在将来(我仍在尝试获取补丁),将出现一个CLOCK_MONOTONIC_RAW,它将不会完全被修改,并且将与硬件计数器具有线性相关性。” 我认为_RAW时钟从未进入内核(除非将其重命名为_HR,但我的研究表明也放弃了这种努力)。
Tony Delroy

41

英特尔处理器的高分辨率,低开销时序

如果您使用的是Intel硬件,则以下是读取CPU实时指令计数器的方法。它会告诉您自处理器启动以来执行的CPU周期数。这可能是您可以用来进行性能评估的最细粒度的计数器。

请注意,这是CPU周期数。在Linux上,您可以从/ proc / cpuinfo获取CPU速度,然后除以秒数。将其转换为双精度非常方便。

当我在盒子上运行它时,我得到

11867927879484732
11867927879692217
it took this long to call printf: 207485

这是英特尔开发人员指南,提供了大量细节。

#include <stdio.h>
#include <stdint.h>

inline uint64_t rdtsc() {
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx");
    return (uint64_t)hi << 32 | lo;
}

main()
{
    unsigned long long x;
    unsigned long long y;
    x = rdtsc();
    printf("%lld\n",x);
    y = rdtsc();
    printf("%lld\n",y);
    printf("it took this long to call printf: %lld\n",y-x);
}

11
请注意,TSC可能不总是在内核之间同步,在处理器进入低功耗模式时(可能无法知道它的执行方式)可能会停止或更改其频率,并且通常并不总是可靠的。内核能够检测何时可靠,检测其他替代方案,例如HPET和ACPI PM计时器,并自动选择最佳方案。始终使用内核进行计时是个好主意,除非您确实确定TSC稳定且单调。
CesarB

12
Core及更高Intel平台上的TSC在多个CPU之间同步,以恒定频率递增,而与电源管理状态无关。请参阅《英特尔软件开发人员手册》,第1卷。3第18.10节。然而,速率计数器增量是一样的CPU的频率。TSC以“平台的最大解析频率,等于可扩展总线频率和最大解析总线比率的乘积”递增。3第18.18.5节。您可以从CPU的型号专用寄存器(MSR)中获得这些值。
sstock

7
您可以通过查询CPU的特定于模型的寄存器(MSR)来获得可伸缩总线频率和最大解析总线比率,如下所示:可伸缩总线频率== MSR_FSB_FREQ [2:0] id 0xCD,最大解析总线比率== MSR_PLATFORM_ID [12: 8] id 0x17。请查阅Intel SDM Vol.3附录B.1解释寄存器值。您可以在Linux上使用msr-tools查询寄存器。 kernel.org/pub/linux/utils/cpu/msr-tools
sstock

1
CPUID在第一RDTSC条指令之后和执行基准测试代码之前,您的代码不应该再次使用吗?否则,如何阻止基准测试代码在first之前/与之并行执行RDTSC,从而导致RDTSCdelta中的代表性不足?
Tony Delroy

18

@伯纳德:

我不得不承认,您的大多数例子都是直截了当的。它确实可以编译,但是似乎可以正常工作。这对SMP系统或SpeedStep安全吗?

这是一个很好的问题……我认为代码还可以。从实际的角度来看,我们每天都在我的公司中使用它,并且我们运行在各种各样的设备上,所有东西都由2-8个内核组成。当然,是YMMV等,但是它似乎是一种可靠且开销低的(因为它不会使上下文切换到系统空间)计时方法。

通常它是如何工作的:

  • 声明代码块为汇编程序(并且是易失性的,因此优化程序将使其保持独立)。
  • 执行CPUID指令。除了获取一些CPU信息(我们不做任何事情)之外,它还同步CPU的执行缓冲区,以使时序不受乱序执行的影响。
  • 执行rdtsc(读取时间戳)执行。这将获取自重置处理器以来执行的机器周期数。这是一个64位的值,因此,以当前的CPU速度,它将每194年左右一次。有趣的是,在原始的Pentium参考中,他们注意到它每5800年左右就出现一次。
  • 最后两行将寄存器中的值存储到变量hi和lo中,并将其放入64位返回值中。

具体说明:

  • 乱序执行会导致不正确的结果,因此我们执行“ cpuid”指令,该指令除了为您提供有关cpu的信息外,还会同步任何乱序指令的执行。

  • 大多数操作系统在启动时都会同步CPU上的计数器,因此答案在几纳秒内就可以了。

  • 休眠注释可能是正确的,但实际上,您可能不在乎跨休眠边界的时间。

  • 关于速度变化:较新的Intel CPU补偿速度变化并返回调整后的计数。我快速浏览了我们网络上的某些设备,发现只有一个没有它的设备:运行某些旧数据库服务器的Pentium 3。(这些是linux机器,所以我检查了:grep constant_tsc / proc / cpuinfo)

  • 我不确定AMD CPU,我们主要是Intel商店,尽管我知道我们的一些低级系统专家对AMD进行了评估。

希望这能满足您的好奇心,这是一个有趣且(IMHO)尚未充分研究的编程领域。您知道Jeff和Joel在谈论程序员是否应该了解C吗?我对他们大喊:“嘿,忘记那些高级C东西了。如果您想知道计算机在做什么,则应该学习汇编程序!”


1
...内核人们一直试图让人们停止使用rdtsc一段时间...并且通常避免在内核中使用它,因为那样太不可靠了。
Spudd86 2010年

1
作为参考,我提出的问题(在单独的答复中,在发表评论之前)是:“我必须承认,您的大多数示例都直接出现在我的头上。它确实可以编译,并且似乎可以正常工作。 SMP系统还是SpeedStep?”
伯纳德



9

因此,它明确表示微秒,但未指定系统时钟的分辨率。我想在这种情况下,分辨率意味着将如何增加最小的数量?

数据结构被定义为以微秒为度量单位,但这并不意味着时钟或操作系统实际上能够精确地进行测量。

就像其他人所建议的那样,这gettimeofday()是不好的,因为设置时间可能会导致时钟偏斜并影响您的计算。 clock_gettime(CLOCK_MONOTONIC)是您想要的,clock_getres()它将告诉您时钟的精度。


因此,当夏令时的gettimeofday()向前或向后跳转时,代码中会发生什么?
mpez0

3
clock_gettime仅在最新的Linux上存在。其他系统只有gettimeofday()
vitaly.v.ch 09/12 / 18,16

8

gettimeofday()的实际分辨率取决于硬件体系结构。英特尔处理器以及SPARC机器均提供可测量微秒的高分辨率计时器。其他硬件体系结构则取决于系统的计时器,该计时器通常设置为100 Hz。在这种情况下,时间分辨率将不太准确。

我从高分辨率时间测量和计时器(第一部分)获得了此答案


6

该答案提到了时钟调整的问题。C ++ 11的<chrono>库中同时解决了保证滴答单位的问题和调整时间的问题。

std::chrono::steady_clock保证时钟不被调整,而且时钟将以相对于实时的恒定速率前进,因此诸如SpeedStep之类的技术必须不影响时钟。

您可以通过转换为std::chrono::duration专业化之一来获取类型安全单位,例如std::chrono::microseconds。使用此类型时,刻度值使用的单位没有任何歧义。但是,请记住,时钟不一定具有此分辨率。您可以将持续时间转换为阿秒,而实际上并不需要精确的时钟。


4

根据我的经验以及通过互联网阅读的内容,答案是“不”,不能保证。它取决于CPU速度,操作系统,Linux的风格等。


3

在SMP系统中,读取RDTSC是不可靠的,因为每个CPU都维护自己的计数器,并且不能保证每个计数器都相对于另一个CPU同步。

我可能建议尝试clock_gettime(CLOCK_REALTIME)。posix手册指示应在所有兼容的系统上实施。它可以提供十亿分之一秒的计数,但是您可能需要检查clock_getres(CLOCK_REALTIME)系统以查看实际分辨率是多少。


clock_getres(CLOCK_REALTIME)不会给出真正的分辨率。它总是返回“1纳秒”(十亿分之一秒)时hrtimers可用,检查include/linux/hrtimer.h文件define HIGH_RES_NSEC 1(更多在stackoverflow.com/a/23044075/196561
osgx
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.