我正在将最初为 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 作为频率,效果很好 在我的机器上, ,给我一个 64 位变量,其中包含 uSeconds 自程序启动以来。

所以 这是便携式的吗? 我不想发现如果内核以某种方式或类似方式编译,它的工作方式会有所不同。不过,我觉得它不能移植到 Linux 以外的地方,对此我很满意。

有帮助吗?

解决方案

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

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

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

其他提示

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

如果您使用的是 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);
}

@伯纳德:

我不得不承认,你的大部分例子都超出了我的理解范围。它确实可以编译,而且似乎可以工作。这对于 SMP 系统或 SpeedStep 安全吗?

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

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

  • 将代码块声明为汇编器(并且波动性,因此优化器将不理会)。
  • 执行CPUID指令。除了获取一些CPU信息(我们不做任何事情)之外,它还同步CPU的执行缓冲区,以免时间不受排序执行的影响。
  • 执行rdtsc(读取时间戳)执行。这可以获取自处理器重置以来执行的机器周期的数量。这是一个64位的值,因此,使用当前的CPU速度,它将每194年左右包装。有趣的是,在原始的奔腾参考文献中,他们注意到它每5800年左右。
  • 最后几行将寄存器的值存储到变量HI和LO中,并将其放入64位返回值中。

具体注意事项:

  • 序列执行可能会导致错误的结果,因此我们执行“ CPUID”指令,除了向您提供有关CPU的一些信息外,还可以同步任何订购的指令执行。

  • 大多数操作系统启动时CPU上的计数器同步,因此答案对几个纳米秒内的次数很好。

  • 冬眠的评论可能是正确的,但是实际上,您可能不在乎整个休眠界的时间。

  • 关于速度步:较新的英特尔CPU弥补了速度变化并返回调整后的计数。我对网络上的一些盒子进行了快速扫描,只发现一个没有它的盒子:运行某些旧数据库服务器的 Pentium 3。(这些是 Linux 盒子,所以我检查了:grep Constant_tsc /proc/cpuinfo)

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

希望这能满足您的好奇心,这是一个有趣且(IMHO)的编程领域。您知道杰夫和乔尔什么时候在谈论程序员是否应该知道C?我在对他们大喊:“嘿,忘了那个高级的c东西...如果您想知道计算机在做什么,汇编器就是您应该学到的!”

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

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

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

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

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

我从以下位置获得了这个答案 高分辨率时间测量和计时器,第 I 部分

这个答案 提到调整时钟的问题。保证刻度单位的问题和调整时间的问题都在 C++11 中解决了 <chrono> 图书馆。

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

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top