gettimeofday()保证是微秒分辨率吗?

Bernard 89 unix linux winapi timer visual-c++

我正在将一个最初为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之外的其他东西.

Louis Brandy.. 56

也许.但是你有更大的问题.gettimeofday()如果您的系统上有更改计时器的进程(即ntpd),则可能导致错误的计时.不过,在"正常"的Linux上,我相信分辨率gettimeofday()为10us.因此,它可以根据系统上运行的进程向前和向后跳转.这有效地解决了你的问题.

您应该研究clock_gettime(CLOCK_MONOTONIC)时间间隔.由于诸如多核系统和外部时钟设置之类的问题,它会受到几个问题的影响.

另外,查看clock_getres()功能.

  • @ vitaly.v.ch它是POSIX所以它不仅仅是Linux而且是'新手'?甚至像红帽企业Linux这样的"企业"发行版也是基于2.6.18,它有clock_gettime所以没有,不是很新..(RHEL的联机帮助页日期是2004年3月12日所以它已经存在了一段时间),除非你是谈论真正的老内核WTF你的意思是? (3认同)
  • 从Linux的常见问题解答中获取lock_gettime(参见David Schlosnagle的回答)"CLOCK_MONOTONIC ...由NTP通过adjtimex()进行频率调整.将来(我还在努力获得补丁)会有一个CLOCK_MONOTONIC_RAW不会完全修改,并与硬件计数器线性相关." 我不认为_RAW时钟曾经进入内核(除非它被重命名为_HR,但我的研究表明,努力也被放弃了). (2认同)

Mark Harriso.. 41

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

如果您使用的是英特尔硬件,请按以下步骤阅读CPU实时指令计数器.它将告诉您自处理器启动以来执行的CPU周期数.这可能是您可以获得的最佳粒度计数器,用于性能测量.

请注意,这是CPU周期数.在linux上,您可以从/ proc/cpuinfo获取CPU速度并除以获得秒数.将其转换为double是非常方便的.

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

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

  • 请注意,TSC可能并不总是在内核之间同步,可能会在处理器进入低功耗模式时停止或更改其频率(并且您无法知道它是这样做的),并且通常并不总是可靠的.内核能够检测何时可靠,检测其他备选方案,如HPET和ACPI PM计时器,并自动选择最佳备选方案.除非您确定TSC稳定且单调,否则始终使用内核进行计时是一个好主意. (11认同)
  • 核心及以上英特尔平台上的TSC在多个CPU*和*之间以恒定频率同步,与电源管理状态无关.参见英特尔软件开发人员手册,第1卷.3第18.10节.但是,计数器递增的速率*与CPU的频率不相同.TSC以"平台的最大分辨率,等于可扩展总线频率和最大分辨率总线比率的乘积"递增"英特尔软件开发人员手册"第1卷.3第18.18.5节.您可以从CPU的模型特定寄存器(MSR)中获取这些值. (11认同)
  • 您可以通过查询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来查询寄存器.http://www.kernel.org/pub/linux/utils/cpu/msr-tools/ (7认同)

Mark Harriso.. 18

@Bernard:

我不得不承认,你的大部分例子都是我的头脑.它确实可以编译,但似乎也可以工作.这对SMP系统或SpeedStep安全吗?

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

一般来说它是如何工作的:

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

具体说明:

  • 乱序执行会导致错误的结果,所以我们执行"cpuid"指令,除了给你一些关于cpu的信息外,还会同步任何无序指令执行.

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

  • 冬眠评论可能是正确的,但实际上你可能并不关心冬眠边界的时间安排.

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

  • 我不太确定AMD CPU,我们主要是英特尔商店,虽然我知道我们的一些低级系统大师做过AMD评估.

希望这能满足您的好奇心,这是一个有趣且(恕我直言)未充分研究的编程领域.你知道Jeff和Joel何时谈论程序员是否应该知道C?我在对他们大喊大叫,"嘿,忘记那些高级C的东西......如果你想知道计算机在做什么,你应该学习汇编程序!"


David Schlos.. 14

您可能对Linux FAQ感兴趣clock_gettime(CLOCK_REALTIME)


Vincent Robe.. 11

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


Joe Shaw.. 10

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

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

像其他人建议的一样,gettimeofday()很糟糕,因为设置时间会导致时钟偏差并导致计算失败. clock_gettime(CLOCK_MONOTONIC)是你想要的,并clock_getres()会告诉你时钟的精确度.

  • clock_gettime仅出现在最新的Linux上.其他系统只有gettimeofday() (3认同)

CodingWithou.. 9

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

我从高分辨率时间测量和定时器,第一部分获得了这个答案


CodingWithou.. 5

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


bames53.. 5

这个答案提到了时钟被调整的问题.保证刻度单位的问题以及时间调整的问题都可以通过C++ 11来解决<chrono>.

时钟std::chrono::steady_clock保证不会被调整,而且它将以相对于实时的恒定速率前进,因此SpeedStep等技术不得影响它.

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