【问题标题】:rdtsc, too many cyclesrdtsc,循环次数过多
【发布时间】:2012-01-09 12:03:16
【问题描述】:
#include <stdio.h>
static inline unsigned long long tick() 
{
        unsigned long long d;
        __asm__ __volatile__ ("rdtsc" : "=A" (d) );
        return d;
}

int main()
{
        long long res;
        res=tick();

        res=tick()-res;
        printf("%d",res);
        return 0;
}

我用 gcc 编译了这段代码,并进行了 -O0 -O1 -O2 -O3 优化。我总是得到 2000-2500 个周期。谁能解释这个输出的原因?如何度过这些周期?

第一个函数“tick”是错误的。这是正确的

函数“tick”的另一个版本

static __inline__ unsigned long long tick()
{
  unsigned hi, lo;
  __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
  return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

这是 -O3 的汇编代码

 .file  "rdtsc.c"
.section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%d"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    subl    $40, %esp
    movl    %ecx, -16(%ebp)
    movl    %ebx, -12(%ebp)
    movl    %esi, -8(%ebp)
    movl    %edi, -4(%ebp)
#APP
# 6 "rdtsc.c" 1
    rdtsc
# 0 "" 2
#NO_APP
    movl    %edx, %edi
    movl    %eax, %esi
#APP
# 6 "rdtsc.c" 1
    rdtsc
# 0 "" 2
#NO_APP
    movl    %eax, %ecx
    movl    %edx, %ebx
    subl    %esi, %ecx
    sbbl    %edi, %ebx
    movl    %ecx, 4(%esp)
    movl    %ebx, 8(%esp)
    movl    $.LC0, (%esp)
    call    printf
    movl    -16(%ebp), %ecx
    xorl    %eax, %eax
    movl    -12(%ebp), %ebx
    movl    -8(%ebp), %esi
    movl    -4(%ebp), %edi
    movl    %ebp, %esp
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size   main, .-main
    .ident  "GCC: (Debian 4.3.2-1.1) 4.3.2"
    .section    .note.GNU-stack,"",@progbits

这是CPU

processor   : 0
vendor_id   : GenuineIntel
cpu family  : 15
model       : 4
model name  : Intel(R) Xeon(TM) CPU 3.00GHz
stepping    : 3
cpu MHz     : 3000.105
cache size  : 2048 KB
fdiv_bug    : no
hlt_bug     : no
f00f_bug    : no
coma_bug    : no
fpu     : yes
fpu_exception   : yes
cpuid level : 5
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss constant_tsc up pebs bts pni
bogomips    : 6036.62
clflush size    : 64

【问题讨论】:

  • 很多暗示你数不过来。改为使用许多。
  • rdtsc 写入 %edx:%eax`。你的 tick() 函数是错误的。
  • 与第二个功能相同的输出
  • 确保您了解为什么rdtsc 不是可靠的计时器。
  • 当我运行你的代码时,我几乎总是得到 42。Douglas Adams 会很高兴的。 :-)

标签: c assembly x86 rdtsc


【解决方案1】:

获得大量的原因有很多:

  • 操作系统进行了上下文切换,您的进程进入睡眠状态。
  • 发生磁盘寻道,您的进程进入睡眠状态。
  • ...关于您的流程可能被忽略的各种原因。

注意rdtsc对于没有工作的计时并不是特别可靠,因为:

  • 处理器速度可能会发生变化,因此周期的长度(以秒为单位)会发生变化。
  • 对于给定的瞬间,不同的处理器可能有不同的 TSC 值。

大多数操作系统都有高精度时钟或计时方法。例如,Linux 上的clock_gettime,尤其是单调时钟。 (也要了解挂钟和单调时钟之间的区别:挂钟可以向后移动——即使在 UTC 中也是如此。)在 Windows 上,我认为建议是 QueryHighPerformanceCounter。通常,这些时钟提供的精度足以满足大多数需求。


此外,查看程序集,您似乎只能得到 32 位的答案:我没有看到 %edxrdtsc 之后得到保存。


运行您的代码,我使用 CLOCK_MONOTONIC 获得了 120-150 ns 的 clock_gettime 和 70-90 个周期的 rdtsc (全速约 20 ns,但我怀疑处理器的时钟已下降,这就是真的大约 50 ns)。 (在 laptop 台式机上(该死的 SSH,忘记我在哪台机器上!)CPU 使用率保持在 20% 左右)确定你的机器没有卡住吗?

【讨论】:

  • struct timespec ts1,ts2; clock_gettime(CLOCK_MONOTONIC,&amp;ts1); clock_gettime(CLOCK_MONOTONIC,&amp;ts2); ts2.tv_nsec-ts1.tv_nsec ~8000 太大了。
  • =A 从 edx 和 eax 复制值。
  • @mifki:如果你这么说:也许 OP 编译错误。你看到%edx 被保存在程序集中了吗?
  • @Thanatos 再次:如果编译为 32 位,=A 约束会将 eax 和 edx 复制到指定位置。
  • @eXXXXXXXXXXX:我在英特尔酷睿 i7 上获得了 ~130 范围内的差异。
【解决方案2】:

只是一个想法-也许这两个 rdtsc 指令在不同的内核上执行? rdtsc 值可能会因内核而略有不同。

【讨论】:

  • 只有核心,由/sys/devices/system/cpu/判断
【解决方案3】:

您的操作系统似乎禁用了用户空间中 RDTSC 的执行。而且您的应用程序必须切换到内核并返回,这需要很多周期。

这是来自英特尔软件开发人员手册:

在受保护或虚拟 8086 模式下,时间戳禁用 (TSD) 标志在 寄存器 CR4 限制 RDTSC 指令的使用如下。当 TSD 标志 很清楚,RDTSC指令可以在任何特权级执行;当国旗 设置,指令只能在特权级别 0 执行。(当在实地址 模式下,RDTSC 指令始终处于启用状态。)

编辑:

回答aix的评论,我解释一下为什么TSD最有可能是这里的原因。

我只知道程序执行一条指令比平时更长的这些可能性:

  1. 在某个模拟器下运行,
  2. 使用自修改代码,
  3. 上下文切换,
  4. 内核开关。

前两个原因通常不能将执行延迟超过几百个周期。 2000-2500 周期对于上下文/内核切换更为典型。但实际上不可能在同一个地方多次捕获上下文切换。所以应该是内核开关。这意味着任何一个程序都在调试器下运行,或者用户模式下不允许使用 RDTSC。

操作系统禁用 RDTSC 的最可能原因可能是安全性。有人尝试使用 RDTSC 破解加密程序。

【讨论】:

  • 我没有对答案投票,但我认为如果你说明为什么你认为 TSD 可能是罪魁祸首,它会得到改善。
  • @aix 我已经解释过了。谢谢。
  • @Evgeny Kluev 和 clock_gettime 类似的解释?我使用CLOCK_MONOTONIC id 获得了 8000 纳秒。这是巨大的=/
  • @eXXXXXXXXXXX clock_gettime 最有可能由内核执行,因此至少需要 2000 个周期。也许更多。但是 8000 纳秒似乎太高了——我无法解释。
  • @eXXXXXXXXXXX 测量短时间间隔的唯一方法可能是说服您的系统管理员允许 RDTSC(因为系统只有一个核心,因此该系统没有安全风险)。
【解决方案4】:

我已经在几个运行在不同 Intel CPU 上的 Linux 发行版上尝试了您的代码(诚然,它们都比您使用的 Pentium 4 HT 630 更新)。在所有这些测试中,我得到了 25 到 50 个周期之间的值。

我唯一与所有证据一致的假设是,您在虚拟机而不是裸机上运行操作系统,并且 TSC 正在虚拟化。

【讨论】:

    【解决方案5】:

    指令缓存未命中? (这是我的猜测)

    还有,可能,

    在虚拟化系统中切换到管理程序? 程序引导程序的残余(包括同一 CPU 上的网络活动)?

    致 Thanatos:在 2008 年以后的系统上,rdtsc() 是一个挂钟,不随频率步长变化。

    你能试试这个小代码吗?

    int main()
    {   
        long long res;
    
        fflush(stdout);           // chnage the exact timing of stdout, in case there is something to write in a ssh connection, together with its interrupts
    
        for (int pass = 0; pass < 2; pass++)
        {
        res=tick();
        res=tick()-res;
        }
        printf("%d",res);     // ignore result on first pass, display the result on second pass.
        return 0;
    }
    

    【讨论】:

    • 从技术上讲,这不是答案,而是更多的后续参与。但是,您没有足够的代表发表评论,看起来您正在积极参与 OP(并且似乎有知识来回答这个问题),所以我不会删除这篇文章。如果您没有收到 OP 的回复,请将其转换为仅假设您所说的答案 - 否则可能会被删除。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-26
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 2019-05-06
    相关资源
    最近更新 更多