gettimeofday()保证具有微秒的分辨率吗?

所以我发现自己将一个原本为Win32 API编写的游戏移植到Linux上(并且将Win32端口的OS X端口移植到Linux)。自从进程启动后,我通过给出uSeconds来实现 QueryPerformanceCounter

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以外的其他产品。

0
额外 编辑
意见: 8

10 答案

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

0
额外

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

I obtained this answer from High Resolution Time Measurement and Timers, Part I

0
额外

也许。但你有更大的问题。如果系统上有更改定时器的进程(即ntpd),gettimeofday()可能会导致错误的定时。然而,在“正常”的linux上,我相信 gettimeofday()的解析是10us。它可以根据系统上运行的进程来前进和后退以及跳转。这有效地使你的问题没有答案。

您应该查看 clock_gettime(CLOCK_MONOTONIC)获取定时间隔。由于诸如多核系统和外部时钟设置之类的东西,它遭遇少了几个问题。

另请查看 clock_getres()函数。

0
额外
它于2001年推出,但在2008年POSIX之前不是强制性的。
额外 作者 R..,
从lock_gettime的Linux常见问题解答(请参阅David Schlosnagle的回答)“CLOCK_MONOTONIC ...通过NTP通过adjtimex()进行频率调整。将来(我仍然试图获取该补丁)会有CLOCK_MONOTONIC_RAW完全可以修改,并且与硬件计数器有直线关系。“我不认为_RAW时钟曾经将它加入内核(除非它被重命名为_HR,但是我的研究表明这种努力也被放弃了)。
额外 作者 Tony Delroy,
@ vitaly.v.ch它是POSIX,所以它不仅仅是Linux和'newist'?即使像红帽企业Linux这样的'企业'发行版都基于2.6.18,它具有clock_gettime,所以没有,不是很新。(RHEL中的手册页日期是2004年3月 - 12月,因此它已经存在了一段时间)除非你谈论真正的破旧老内核WTF你的意思是?
额外 作者 Spudd86,
clock_gettime仅存在于最新的Linux上。其他系统只有gettimeofday()
额外 作者 vitaly.v.ch,
clock_gettime在2001年被包含在POSIX中。就我所知,当前clock_gettime()在Linux 2.6和qnx中实现。但是linux 2.4目前在许多生产系统中使用。
额外 作者 vitaly.v.ch,

Wine实际上使用gettimeofday()来实现QueryPerformanceCounter(),众所周知,许多Windows游戏都可以在Linux和Mac上运行。

Starts http://source.winehq.org/source/dlls/kernel32/cpu.c#L312

leads to http://source.winehq.org/source/dlls/ntdll/time.c#L448

0
额外

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

如果您使用的是英特尔硬件,请阅读如何读取CPU实时指令计数器。它会告诉你处理器启动后执行的CPU周期数。这可能是您可以获得的性能测量最好的计数器。

请注意,这是CPU周期数。在linux上,你可以从/ proc / cpuinfo获得CPU速度并分割得到秒数。把它转换成double是非常方便的。

当我在箱子上运行这个时,我会得到

11867927879484732
11867927879692217
it took this long to call printf: 207485

以下是英特尔开发人员指南,其中提供了大量的细节。

#include 
#include 

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);
}
0
额外
在第一个 RDTSC 指令之后,执行被基准测试的代码之前,你的代码不应该再次使用 CPUID 吗?否则,什么是停止在第一个 RDTSC 之前/并行执行的基准代码,并且因此在 RDTSC 中没有表示代码?
额外 作者 Tony Delroy,
请注意,TSC可能并不总是在内核之间同步,当处理器进入低功耗模式时可能会停止或改变其频率(并且您无法知道它是如此),并且通常并不总是可靠的。内核能够检测何时可靠,检测到其他替代方案,如HPET和ACPI PM定时器,并自动选择最佳方案。除非您确定TSC稳定且单调,否则始终使用内核进行定时是一个好主意。
额外 作者 CesarB,
核心及以上英特尔平台上的TSC在跨越多个CPU 增量的情况下以独立于电源管理状态的恒定频率进行同步。请参阅英特尔软件开发人员手册,卷。 3第18.10节。然而,计数器递增的速率与CPU的频率不同。 TSC以平台的最大解析频率递增,这等于可伸缩总线频率和最大解析总线比率的乘积。英特尔软件开发人员手册,卷。 3第18.18.5节。您可以从CPU的型号专用寄存器(MSR)中获取这些值。
额外 作者 sstock,
通过查询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,

@Bernard:

我不得不承认,你们的大部分例子都直指我的脑海。它确实编译,似乎工作,但。这对于SMP系统或SpeedStep是否安全?

这是一个很好的问题......我认为代码没问题。 从实际的角度来看,我们每天都在我的公司使用它, 我们运行的是一系列相当广泛的盒子,从2-8个核心开始。 当然,YMMV等,但它似乎是一个可靠和低开销 (因为它不会将上下文切换到系统空间)方法 的时机。

一般来说它的工作原理是:

  • 声明代码块是汇编程序(并且是易失性的,所以 优化器将保持独立)。
  • 执行CPUID指令。除了获取一些CPU信息 (我们不做任何事),它同步CPU的执行缓冲区 以便时序不受乱序执行的影响。
  • 执行rdtsc(读取时间戳)执行。这会提取数量 自处理器重置以来执行的机器周期。这是一个64位的 值,所以对于当前的CPU速度,它将每隔194年左右回绕一次。 有趣的是,在最初的奔腾引用中,他们注意到它包裹了每一个 5800年左右。
  • 最后几行存储寄存器中的值 变量hi和lo,并将其放入64位返回值中。

具体说明:

    乱序执行会导致不正确的结果,所以我们执行 “cpuid”指令除了给你一些信息 关于CPU也会同步任何乱序指令执行。
  • 大多数操作系统在启动时会同步CPU上的计数器,所以 答案在几纳秒之内是好的。

  • 冬眠的评论可能是真实的,但在实践中你 可能不关心冬眠边界的时间。
  • 关于speedstep:较新的Intel CPU可以补偿速度 更改并返回调整的计数。我做了一个快速扫描 我们网络上的一些盒子只发现一盒 没有它:运行一些旧的数据库服务器的Pentium 3。 (这些是linux的盒子,所以我用grep constant_tsc / proc / cpuinfo进行了检查)

  • 我不确定AMD CPU,我们主要是英特尔商店, 尽管我知道我们的一些低级系统专家做了一个 AMD评估。

希望这能满足你的好奇心,这是一个有趣的(恕我直言) 未被充分研究的编程领域。你知道杰夫和乔尔是什么时候 谈论程序员是否应该知道C?我曾是 对他们大喊,“嘿,忘记了C级的东西......汇编 如果你想知道电脑是什么的话,你应该学会什么 这样做!”

0
额外
作为参考,我问的问题(在一个单独的回复 - 在评论之前)是:“我不得不承认,你的大部分例子都是我的头脑,它确实能够编译,并且似乎能够工作。 SMP系统还是SpeedStep?“
额外 作者 Bernard,
......内核人员一直试图让人们停止使用rdtsc一段时间......并且通常避免在内核中使用它,因为它不可靠。
额外 作者 Spudd86,
所以它明确表示微秒,但表示系统时钟的分辨率没有指定。在这种情况下,我认为解决方案意味着它的最小数量将如何递增?

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

像其他人一样, gettimeofday()是不好的,因为设置时间可能会导致时钟偏移并推迟计算。 clock_gettime(CLOCK_MONOTONIC)是你想要的, clock_getres()会告诉你时钟的精确度。

0
额外
@ mpez0它没有
额外 作者 Spudd86,
那么gettimeofday()在夏令时向前或向后跳转时,代码中会发生什么?
额外 作者 mpez0,
clock_gettime仅存在于最新的Linux上。其他系统只有gettimeofday()
额外 作者 vitaly.v.ch,

This answer mentions problems with the clock being adjusted. Both your problems guaranteeing tick units and the problems with the time being adjusted are solved in C++11 with the library.

时钟 std :: chrono :: steady_clock 保证不会被调整,而且它会以相对于实时的恒定速率前进,所以像SpeedStep这样的技术一定不会影响它。

您可以通过转换为 std :: chrono :: duration 专业化版本之一,例如 std :: chrono :: microseconds 来获得typesafe单元。使用这种类型时,tick值使用的单位不会含糊不清。但是,请记住,时钟不一定具有此分辨率。您可以将持续时间转换为阿秒,但实际上没有准确的时钟。

0
额外

读取RDTSC在SMP系统中是不可靠的,因为每个CPU都维护自己的计数器,并且不保证每个计数器与其他CPU同步。

我可能会建议尝试 clock_gettime(CLOCK_REALTIME) 。 posix手册表明这应该在所有符合标准的系统上实施。它可以提供纳秒计数,但您可能需要检查系统上的 clock_getres(CLOCK_REALTIME) ,以查看实际分辨率。

0
额外
clock_getres(CLOCK_REALTIME)不会提供真实的分辨率。当hrtimers可用时,它总是返回“1 ns”(1纳秒),检查 include / linux / hrtimer.h 文件定义HIGH_RES_NSEC 1 (更多stackoverflow.com/a/23044075/196561
额外 作者 osgx,
0
额外