gettimeofday()保证微秒的分辨率?
所以我发现自己将一个原本为Win32 API编写的游戏移植到Linux上(把Win32端口的OS X端口移植到Linux上)。 我已经实现QueryPerformanceCounter
通过提供uSeconds自启动过程:
BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount) { gettimeofday(¤tTimeVal, 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的频率, 在我的机器上运行良好,给我一个64位的variables,包含uSeconds
自程序启动。 那么这是便携式? 我不想发现,如果内核是以某种方式编译或者类似的东西的话,它的工作方式会有所不同。 但是,对于Linux以外的其他软件,我没有任何问题。
也许。 但是你有更大的问题。 如果系统上有更改计时器的进程(即ntpd),则gettimeofday()
可能会导致错误的计时。 然而,在“正常”的linux上,我相信gettimeofday()
的parsing是10us。 它可以根据系统上运行的进程,跳转,跳转和跳转。 这有效地使你的问题没有答案。
您应该查看clock_gettime(CLOCK_MONOTONIC)
的时间间隔。 由于诸如多核系统和外部时钟设置之类的问题,它会遇到几个较less的问题。
另外,查看clock_getres()
函数。
英特尔处理器的高分辨率,低开销时序
如果您使用的是英特尔硬件,请阅读如何读取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",yx); }
@Bernard:
我不得不承认,你们的大部分例子都直指我的头脑。 它编译,似乎工作,虽然。 这对SMP系统或SpeedStep是否安全?
这是一个很好的问题…我认为代码是好的。 从实际的angular度来看,我们每天都在公司里使用它,而且我们运行的是一系列非常广泛的盒子,从2-8个核心到一切。 当然,YMMV等,但它似乎是一个可靠和低开销(因为它不会使上下文切换到系统空间)的时间方法。
一般来说它的工作原理是:
- 声明代码块是汇编程序(和易失性,所以优化器将保持独立)。
- 执行CPUID指令。 除了获取一些CPU信息(我们不做任何事情)之外,它还同步CPU的执行缓冲区,以便时序不受乱序执行的影响。
- 执行rdtsc(读取时间戳)执行。 这将获取自处理器重置以来执行的机器周期数。 这是一个64位的值,所以在当前的CPU速度下,它将每隔194年左右。 有趣的是,在原来的奔腾引用中,他们注意到它每5800年左右就会包装一次。
- 最后几行将寄存器中的值存储到variableshi和lo中,并将其放入64位的返回值中。
具体说明:
-
乱序执行可能会导致错误的结果,所以我们执行“cpuid”指令,除了给你一些有关CPU的信息之外,还可以同步任何无序的指令执行。
-
大多数操作系统在启动时会同步CPU上的计数器,所以答案在几纳秒之内。
-
冬眠的评论可能是真实的,但实际上你可能不关心跨冬眠的时间界限。
-
有关speedstep:较新的英特尔CPU补偿速度变化并返回一个调整的计数。 我对我们networking上的一些盒子进行了快速扫描,发现只有一个没有它的盒子:运行一些旧数据库服务器的Pentium 3。 (这些是Linux的盒子,所以我查了:grep constant_tsc / proc / cpuinfo)
-
我不确定AMD的CPU,我们主要是英特尔的商店,尽pipe我知道我们的一些低级系统大师做了AMD评估。
希望这可以满足你的好奇心,这是一个有趣的(恕我直言)未充分研究的编程领域。 你知道杰夫和乔尔在谈论一个程序员是否应该知道C吗? 我对他们大喊,“嘿,忘了C级别的东西……如果你想知道计算机在做什么,就应该学习汇编程序!
您可能对clock_gettime(CLOCK_REALTIME)
Linux常见问题感兴趣
Wine实际上是使用gettimeofday()来实现QueryPerformanceCounter(),并且知道许多Windows游戏都可以在Linux和Mac上运行。
所以它明确表示微秒,但是表示系统时钟的分辨率是未指定的。 在这种情况下,我认为解决问题意味着如何增加最小的数额?
数据结构被定义为以微秒作为度量单位,但这并不意味着时钟或操作系统实际上能够精确地进行测量。
像其他人一样, gettimeofday()
是不好的,因为设置时间可能会导致时钟偏斜,并推迟计算。 clock_gettime(CLOCK_MONOTONIC)
是你想要的, clock_getres()
会告诉你时钟的精度。
gettimeofday()的实际分辨率取决于硬件体系结构。 英特尔处理器以及SPARC机器提供高分辨率计时器,可测量微秒。 其他硬件体系结构回退到系统的定时器,通常设置为100 Hz。 在这种情况下,时间分辨率将不太准确。
我从高分辨率时间测量和定时器,第一部分获得了这个答案
这个答案提到了时钟正在调整的问题。 在C ++ 11中用<chrono>
库解决了保证刻度单位和时间调整问题的问题。
时钟std::chrono::steady_clock
保证不被调整,而且它会以相对于实时的恒定速率前进,所以像SpeedStep这样的技术一定不能影响它。
你可以通过转换为std::chrono::duration
专业化之一,例如std::chrono::microseconds
来获得typesafe单位。 有了这个types,tick值所使用的单位就没有歧义。 但是,请记住,时钟不一定有这个决议。 您可以将持续时间转换为阿秒,而不需要准确的时钟。
根据我的经验和我在互联网上阅读的内容,答案是“不”,但不能保证。 这取决于CPU速度,操作系统,Linux的味道等。
读取RDTSC在SMP系统中是不可靠的,因为每个CPU维护自己的计数器,并且不保证每个计数器通过与另一个CPU同步。
我可能会build议尝试clock_gettime(CLOCK_REALTIME)
。 posix手册指出,这应该在所有兼容系统上实施。 它可以提供纳秒计数,但是您可能需要检查系统上的clock_getres(CLOCK_REALTIME)
以查看实际分辨率。