我们的教师向我们提供了以下代码,因此我们可以测量一些算法性能:
#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.这排除了虚拟机上的问题,我认为这是最初的问题。
为什么会发生这种情况的任何想法以及如何解决?
答案 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指令(取决于客户操作系统):
注意,在#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(而不是最后一个)并使用两个双精度进行乘法