【问题标题】:Is gettimeofday() guaranteed to be of microsecond resolution?gettimeofday() 是否保证为微秒级分辨率?
【发布时间】:2010-09-05 05:34:17
【问题描述】:

我正在将最初为 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 以外的其他东西。

【问题讨论】:

    标签: linux winapi visual-c++ unix timer


    【解决方案1】:

    也许吧。但是你有更大的问题。 gettimeofday() 如果您的系统上存在更改计时器的进程(即 ntpd),可能会导致不正确的计时。不过,在“普通”Linux 上,我相信gettimeofday() 的分辨率是 10us。因此,它可以根据系统上运行的进程向前和向后跳跃和时间。这有效地回答了您的问题。

    您应该查看clock_gettime(CLOCK_MONOTONIC) 的时间间隔。由于多核系统和外部时钟设置等原因,它遇到的问题较少。

    另外,查看clock_getres() 函数。

    【讨论】:

    • clock_gettime 仅存在于最新的 Linux 上。其他系统只有 gettimeofday()
    • @vitaly.v.ch 它是 POSIX,所以它不仅仅是 Linux 和“newist”?甚至像 Red Hat Enterprise Linux 这样的“企业”发行版也是基于 2.6.18 的,它有 clock_gettime 所以不,不是很新..(RHEL 中的手册页日期是 2004-March-12 所以它已经存在了一段时间)除非你是谈论真正的旧内核 WTF 是什么意思?
    • clock_gettime 于 2001 年被包含在 POSIX 中。据我所知,目前在 Linux 2.6 和 qnx 中实现了 clock_gettime()。但是目前很多生产系统都在使用linux 2.4。
    • 它于 2001 年推出,但直到 POSIX 2008 才强制使用。
    • 来自 lock_gettime 的 Linux 常见问题解答(参见 David Schlosnagle 的回答)“CLOCK_MONOTONIC...是 NTP 通过 adjtimex() 调整的频率。将来(我仍在尝试获取补丁)将有一个 CLOCK_MONOTONIC_RAW 根本不会被修改,并且与硬件计数器具有线性相关性。”我认为 _RAW 时钟从未进入内核(除非它被重命名为 _HR,但我的研究表明这种努力也被放弃了)。
    【解决方案2】:

    适用于英特尔处理器的高分辨率、低开销时序

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

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

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

    11867927879484732
    11867927879692217
    it took this long to call printf: 207485
    

    Intel developer's guide 提供了大量细节。

    #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);
    }
    

    【讨论】:

    • 请注意,TSC 可能并不总是在内核之间同步,可能会在处理器进入低功耗模式时停止或更改其频率(您无法知道它这样做了),并且通常是并不总是可靠的。内核能够检测何时是可靠的,检测其他替代方案,如 HPET 和 ACPI PM 计时器,并自动选择最好的一个。除非您确实确定 TSC 稳定且单调,否则始终使用内核进行计时是个好主意。
    • Core 及以上 Intel 平台上的 TSC 在多个 CPU 之间同步并且以独立于电源管理状态的恒定频率递增。请参阅英特尔软件开发人员手册,卷。 3 第 18.10 节。然而,计数器递增的速率与 CPU 的频率相同。 TSC 以“平台的最大解析频率,这等于可扩展总线频率和最大解析总线比率的乘积”英特尔软件开发人员手册,第 1 卷递增。 3 第 18.18.5 节。您可以从 CPU 的特定型号寄存器 (MSR) 中获取这些值。
    • 您可以通过查询 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
    • 您的代码不应该在第一条RDTSC 指令之后和执行基准测试代码之前再次使用CPUID 吗?否则,如何阻止基准代码在第一个 RDTSC 之前/并行执行,因此在 RDTSC delta 中的代表性不足?
    【解决方案3】:

    @伯纳德:

    我不得不承认,你的大部分例子都让我一头雾水。不过,它确实可以编译,并且似乎可以工作。这对于 SMP 系统或 SpeedStep 是否安全?

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

    一般来说是这样的:

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

    具体说明:

    • 乱序执行会导致不正确的结果,所以我们执行 “cpuid”指令除了给你一些信息 关于 cpu 还同步任何乱序指令的执行。

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

    • hibernating 评论可能是真的,但实际上你 可能不关心跨越休眠边界的时间。

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

    • 我不确定 AMD CPU,我们主要是 Intel 商店, 虽然我知道我们的一些低级系统专家做过 AMD 评估。

    希望这能满足您的好奇心,这是一个有趣且(恕我直言) 编程研究不足的领域。你知道杰夫和乔尔什么时候 谈论程序员是否应该了解C?我曾是 对他们大喊,“嘿,忘记那些高级 C 的东西......汇编程序 如果您想知道计算机是什么,您应该学习什么 做!”

    【讨论】:

    • ...内核人员一直试图让人们停止使用 rdtsc 一段时间...并且通常避免在内核中使用它,因为它就是那么不可靠。
    • 作为参考,我问的问题(在单独的回复中——在 cmets 之前)是:“我不得不承认,你的大部分例子都让我一头雾水。它确实编译了,而且似乎不过可以。这对 SMP 系统或 SpeedStep 是否安全?"
    【解决方案4】:

    【讨论】:

      【解决方案5】:

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

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

      导致http://source.winehq.org/source/dlls/ntdll/time.c#L448

      【讨论】:

        【解决方案6】:

        所以它明确表示微秒,但表示系统时钟的分辨率未指定。我想这种情况下的分辨率意味着它会增加的最小量是多少?

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

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

        【讨论】:

        • 那么当 gettimeofday() 在夏令时向前或向后跳转时,代码中会发生什么?
        • clock_gettime 仅存在于最新的 Linux 上。其他系统只有 gettimeofday()
        【解决方案7】:

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

        我从High Resolution Time Measurement and Timers, Part I得到这个答案

        【讨论】:

          【解决方案8】:

          This answer 提到了时钟调整的问题。在 C++11 中使用 &lt;chrono&gt; 库解决了保证刻度单位的问题和调整时间的问题。

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

          您可以通过转换为std::chrono::duration 特化之一来获得类型安全单位,例如std::chrono::microseconds。对于这种类型,刻度值使用的单位没有歧义。但是,请记住,时钟不一定具有此分辨率。您可以将持续时间转换为阿秒,而实际上没有那么准确的时钟。

          【讨论】:

            【解决方案9】:

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

            【讨论】:

              【解决方案10】:

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

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

              【讨论】:

              • clock_getres(CLOCK_REALTIME) 不会给出真正的分辨率。当 hrtimer 可用时,它总是返回“1 ns”(一纳秒),检查 include/linux/hrtimer.h 文件中的 define HIGH_RES_NSEC 1(更多信息请参见 stackoverflow.com/a/23044075/196561
              猜你喜欢
              • 1970-01-01
              • 2012-11-12
              • 2011-07-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-01-25
              • 2018-10-29
              • 2016-07-11
              相关资源
              最近更新 更多