使用RDTSC以C计算CPU频率始终返回0

时间:2010-05-11 21:25:19

标签: c cpu clock frequency performance

我们的教师向我们提供了以下代码,因此我们可以测量一些算法性能:

#include <stdio.h>
#include <unistd.h>

static unsigned cyc_hi = 0, cyc_lo = 0;

static void access_counter(unsigned *hi, unsigned *lo) {
    asm("rdtsc; movl %%edx,%0; movl %%eax,%1"
    : "=r" (*hi), "=r" (*lo)
    : /* No input */
    : "%edx", "%eax");
}

void start_counter() {
    access_counter(&cyc_hi, &cyc_lo);
}

double get_counter() {
    unsigned ncyc_hi, ncyc_lo, hi, lo, borrow;
    double result;

    access_counter(&ncyc_hi, &ncyc_lo);

    lo = ncyc_lo - cyc_lo;
    borrow = lo > ncyc_lo;
    hi = ncyc_hi - cyc_hi - borrow;

    result = (double) hi * (1 << 30) * 4 + lo;

    return result;
}

但是,我需要将此代码移植到具有不同CPU频率的计算机上。为此,我试图计算运行代码的机器的CPU频率,如下所示:

int main(void)
{
    double c1, c2;

    start_counter();

    c1 = get_counter();
    sleep(1);
    c2 = get_counter();

    printf("CPU Frequency: %.1f MHz\n", (c2-c1)/1E6);
    printf("CPU Frequency: %.1f GHz\n", (c2-c1)/1E9);

    return 0;
}

问题是结果总是0,我无法理解为什么。我在VMware上作为嘉宾运行Linux(Arch)。

在朋友的机器上(MacBook),它在某种程度上起作用;我的意思是,结果大于0但它是可变的,因为CPU频率没有固定(我们试图修复它但由于某种原因我们无法做到)。他有一台运行Linux(Ubuntu)作为主机的不同机器,它也报告了0.这排除了虚拟机上的问题,我认为这是最初的问题。

为什么会发生这种情况的任何想法以及如何解决?

5 个答案:

答案 0 :(得分:2)

好的,由于其他答案没有帮助,我会尝试详细解释。问题是现代CPU可以不按顺序执行指令。您的代码最初类似于:

rdtsc
push 1
call sleep
rdtsc

现代CPU 必须按照原始顺序执行指令。尽管您的原始订单,CPU(大多数)可以自由执行,如:

rdtsc
rdtsc
push 1
call sleep

在这种情况下,很清楚为什么两个rdtsc之间的差异(至少非常接近)为了0.为了防止这种情况,你需要执行CPU将从不执行的指令重新排列以执行乱序。最常用的指令是CPUID。我链接的另一个答案(如果内存服务)大致从那里开始,关于正确/有效地使用CPUID执行此任务所需的步骤。

当然,Tim Post可能是正确的,并且你也因为虚拟机而看到了问题。尽管如此,正如它现在所说的那样,无法保证您的代码即使在真实硬件上也能正常工作。

编辑:为什么代码 的工作原理:首先,指令 可能无序执行的事实并不能保证它们< em>将成为。其次,sleep的(至少某些实现)可能包含阻止rdtsc在其周围重新排列的序列化指令,而其他指令则不包含(或者可能包含它们,但仅在特定的情况下执行它们) (但未指明)情况)。

你剩下的是几乎任何重新编译都会改变的行为,甚至只是在一次运行和下一次运行之间。它可以连续几十次产生非常准确的结果,然后因某些(几乎)完全无法解释的原因而失败(例如,某些其他过程完全发生的事情)。

答案 1 :(得分:2)

我不能确定你的代码究竟出了什么问题,但你正在为这么简单的指令做一些不必要的工作。我建议您大幅简化rdtsc代码。您不需要进行64位数学运算,并且您不需要将该操作的结果存储为double。您不需要在内联asm中使用单独的输出,您可以告诉GCC使用eax和edx。

以下是此代码的大大简化版本:

#include <stdint.h>

uint64_t rdtsc() {
    uint64_t ret;

# if __WORDSIZE == 64
    asm ("rdtsc; shl $32, %%rdx; or %%rdx, %%rax;"
        : "=A"(ret)
        : /* no input */
        : "%edx"
    );
#else
    asm ("rdtsc" 
        : "=A"(ret)
    );
#endif
    return ret;
}

另外你应该考虑打印掉你从中得到的值,这样你就可以看出你是否已经拿出0或其他东西。

答案 2 :(得分:1)

对于VMWare,请查看the time keeping spec(PDF链接)以及this thread。 TSC指令(取决于客户操作系统):

  • 直接传递给真正的硬件(光伏客户)
  • 计算周期,而 VM正在主机处理器上执行(Windows /等)

注意,在#2中,而 VM正在主机处理器上执行。如果我没记错的话,Xen也会出现同样的现象。从本质上讲,您可以预期代码应该在半虚拟客户端上按预期工作。如果被模仿,那么期望硬件就像一致性完全是不合理的。

答案 3 :(得分:1)

您忘记在asm语句中使用volatile ,因此您要告诉编译器,asm语句每次都会产生相同的输出,就像纯函数一样。 ({volatile仅对没有输出的asm语句是隐式的。)

这说明了为什么精确地为零:编译器通过CSE(公共子表达式消除)在编译时将end-start优化为0

有关__rdtsc()内在函数的信息,请参见Get CPU cycle count?上的答案,@ Mysticial的答案中包含有效的GNU C内联汇编,我将在此处引用:

// prefer using the __rdtsc() intrinsic instead of inline asm at all.
uint64_t rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

这对于32位和64位代码正确有效地工作。

答案 4 :(得分:0)

嗯,我不是肯定的,但我怀疑问题可能在这一行内:

result =(double)hi *(1 <&lt; 30)* 4 + lo;

我怀疑你是否可以安全地在“无符号”中进行如此大的乘法......那通常不是32位数吗? ...只是事实上你不能安全地乘以2 ^ 32并且不得不追加它作为额外的“* 4”添加到最后的2 ^ 30已经暗示了这种可能性......你可能需要将每个子组件hi和lo转换为double(而不是最后一个)并使用两个双精度进行乘法