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

我们的讲师给出了以下代码,因此我们可以测量一些算法性能:

#include  #include  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.这排除了虚拟机上的问题,我认为这是最初的问题。

任何想法为什么会发生这种情况,我该如何解决?

好的,既然其他答案没有帮助,我会尝试更详细地解释。 问题是现代CPU可以不按顺序执行指令。 你的代码开头是这样的:

 rdtsc push 1 call sleep rdtsc 

但是,现代CPU不一定按原始顺序执行指令。 尽管您的原始订单,CPU(大多数)可以自由执行,就像:

 rdtsc rdtsc push 1 call sleep 

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

当然,Tim Post可能是正确的,而且你也会因为虚拟机而遇到问题。 尽管如此,就目前而言,即使在真实硬件上,也无法保证您的代码能够正常工作。

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

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

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

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

 #include  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或其他东西。

至于VMWare,请查看时间保持规范 (PDF链接)以及此线程 。 TSC指令(取决于客户操作系统):

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

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

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

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

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

你忘了在你的asm语句中使用volatile ,所以你告诉编译器asm语句每次产生相同的输出,就像纯函数一样。 ( volatile仅对没有输出的asm语句隐含。)

这就解释了为什么你得到的正好为零:编译器通过CSE(公共子表达式消除)将编译时的end-start优化为0

请参阅我关于获取CPU周期数的答案? 对于__rdtsc()内在函数,@ Mysticial的答案有GNU C inline asm,我在这里引用:

 // 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位代码正常有效。