使用 rdtsc 对英特尔进行汇编程序基准测试给出了奇怪的答案,为什么?

assembler benchmarking on intel using rdtsc is giving strange answers, why?

不久前,我问了一个关于堆栈溢出的问题,并展示了如何在 C++ 中执行 rdtsc 操作码。我最近使用 rdtsc 创建了一个基准函数,如下所示:

inline unsigned long long rdtsc() {
  unsigned int lo, hi;
  asm volatile (
     "cpuid \n"
     "rdtsc" 
   : "=a"(lo), "=d"(hi) /* outputs */
   : "a"(0)             /* inputs */
   : "%ebx", "%ecx");     /* clobbers*/
  return ((unsigned long long)lo) | (((unsigned long long)hi) << 32);
}

typedef uint64_t (*FuncOneInt)(uint32_t n);
/**
     time a function that takes an integer parameter and returns a 64 bit number
     Since this is capable of timing in clock cycles, we won't have to do it a
     huge number of times and divide, we can literally count clocks.
     Don't forget that everything takes time including getting into and out of the
     function.  You may want to time an empty function.  The time to do the computation
     can be compute by taking the time of the function you want minus the empty one.
 */
void clockBench(const char* msg, uint32_t n, FuncOneInt f) {
    uint64_t t0 = rdtsc();
    uint64_t r = f(n);
    uint64_t t1 = rdtsc();
    std::cout << msg << "n=" << n << "\telapsed=" << (t1-t0) << '\n';
}

因此,我假设如果我对一个函数进行基准测试,我将(大致)拥有它执行所需的时钟周期数。我还假设,如果我想减去进入或退出函数所需的时钟周期数,我应该对一个空函数进行基准测试,然后在其中编写一个包含所需代码的函数。

这是一个示例:

uint64_t empty(uint32_t n) {
    return 0;
}

uint64_t sum1Ton(uint32_t n) {
    uint64_t s = 0;
    for (int i = 1; i <= n; i++)
        s += i;
    return s;
}

代码编译使用

g++ -g -O2

我可以理解是否由于中断或其他情况而导致一些错误,但是鉴于这些例程很短,并且 n 被选择得很小,我假设我可以看到实数。但令我惊讶的是,这是两个连续 运行s

的输出
empty n=100 elapsed=438
Sum 1 to n=100  elapsed=887

empty n=100 elapsed=357
Sum 1 to n=100  elapsed=347

一直以来,空函数表明它占用的方式比应有的多。

毕竟函数的进出涉及的指令就那么几条。真正的工作是在循环中完成的。不要在意方差很大的事实。第二个运行,空函数号称用了357个时钟周期,求和用的少,荒谬。

发生了什么事?

Consistently the empty function shows that it takes way more than it should.

您有 cpuid 在时间间隔内 。根据 Agner Fog 的测试,cpuid 在英特尔 Sandybridge 系列 CPUs 上需要 100 到 250 个核心时钟周期(取决于您忽略设置的输入)。 (https://agner.org/optimize/).

但是您不是在测量核心时钟周期,而是在测量 RDTSC 参考周期,这可能会短得多。 (例如,我的 Skylake i7-6700k 空闲频率为 800MHz,但参考时钟频率为 4008MHz。)请参阅 Get CPU cycle count?,了解我在 rdtsc.

上的规范回答尝试

首先预热 CPU,或者 运行 在另一个核心上进行 pause 繁忙循环以使其保持在最大值(假设它是台式机/笔记本电脑双核或四核,所有核心频率都锁定在一起。)


Never mind the fact that the variance is huge. In the second run, the empty function claims to be taking 357 clock cycles and the sum takes less, which is ridiculous.

那个效果是不是也一致?

也许您的 CPU 在打印第 3 行消息期间/之后加速到全速,使最后一个计时区域 运行 快很多? ().

IDK 在 cpuid 之前 eax 和 ecx 中的不同垃圾会有多大影响。将其替换为 lfence 以消除它并使用开销低得多的方式来序列化 rdtsc.