104

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

4

10 回答 10

61

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

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

另外,查看clock_getres()功能。

于 2008-08-01T14:53:47.497 回答
43

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

如果您使用的是 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);
}
于 2008-08-02T08:08:22.230 回答
18

@伯纳德:

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

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

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

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

具体说明:

  • 乱序执行会导致不正确的结果,因此我们执行“cpuid”指令,它除了为您提供有关 cpu 的一些信息外,还同步任何乱序指令的执行。

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

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

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

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

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

于 2008-08-04T00:51:52.843 回答
14

你可能对Linux 常见问题感兴趣clock_gettime(CLOCK_REALTIME)

于 2008-08-18T15:51:01.877 回答
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

于 2008-08-04T14:44:56.950 回答
9

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

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

于 2008-08-01T14:55:08.477 回答
9

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

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

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

于 2008-08-02T17:57:06.033 回答
6

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

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

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

于 2012-06-26T15:57:14.370 回答
4

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

于 2008-08-01T14:46:53.097 回答
3

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

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

于 2008-08-18T15:40:14.173 回答