我在这一行中感到困惑-->
result = (double) hi * (1 << 30) * 4 + lo;
以下代码的:
void access_counter(unsigned *hi, unsigned *lo)
// Set *hi and *lo to the high and low order bits of the cycle
// counter.
{
asm("rdtscp; movl %%edx,%0; movl %%eax,%1" // Read cycle counter
: "=r" (*hi), "=r" (*lo) // and move results to
: /* No input */ // the two outputs
: "%edx", "%eax");
}
double get_counter()
// Return the number of cycles since the last call to start_counter.
{
unsigned ncyc_hi, ncyc_lo;
unsigned hi, lo, borrow;
double result;
/* Get cycle counter */
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;
if (result < 0) {
fprintf(stderr, "Error: counter returns neg value: %.0fn", result);
}
return result;
}
我不能理解的是,为什么hi要乘以2^30,然后再乘以4?然后再加上low?请有人解释一下这行代码中发生了什么。我知道高低包含什么。
简短的答案:
该行将存储为2个32位值的64位整数转换为浮点数。
为什么代码不使用64位整数?好吧,gcc支持64位数字已经有很长一段时间了,但据推测,这段代码早于64位数字。在这种情况下,支持这么大的数字的唯一方法是将它们放入浮点数中。
答案很长:
首先,您需要了解rdtscp是如何工作的。当这个汇编指令被调用时,它做两件事:
1) 将ecx设置为IA32_TSC_AUX MSR。根据我的经验,这通常意味着ecx被设置为零。2) 将edx:eax设置为处理器时间戳计数器的当前值。这意味着计数器的低64位进入eax,而高32位进入edx。
考虑到这一点,让我们看看代码。当从get_counter调用时,access_counter将把edx放在'ncyc_hi'中,把eax放在'sncyc_lo'中然后get_counter将执行以下操作:
lo = ncyc_lo - cyc_lo;
borrow = lo > ncyc_lo;
hi = ncyc_hi - cyc_hi - borrow;
这是干什么的?
由于时间存储在两个不同的32位数字中,如果我们想知道经过了多少时间,我们需要做一些工作来找出旧时间和新时间之间的差异。完成后,结果存储在hi/lo中(再次使用2个32位数字)。
这终于引出了你的问题。
result = (double) hi * (1 << 30) * 4 + lo;
如果我们可以使用64位整数,那么将2个32位值转换为单个64位值将如下所示:
unsigned long long result = hi; // put hi into the 64bit number.
result <<= 32; // shift the 32 bits to the upper part of the number
results |= low; // add in the lower 32bits.
如果你不习惯比特转换,也许这样看会有所帮助。如果lo=1且high=2,则表示为十六进制数:
result = hi; 0x0000000000000002
result <<= 32; 0x0000000200000000
result |= low; 0x0000000200000001
但是,如果我们假设编译器不支持64位整数,那就行不通了。虽然浮点数可以保持那么大的值,但它们不支持移位。因此,我们需要找到一种方法,将"hi"左移32位,而不使用左移。
那么,左移1就等于乘2。左移2等于乘4。左移[省略…]左移32等于乘4294967296。
巧合的是,4294967296==(1<<30)*4。
那么,为什么要用那种复杂的方式来写呢?4294967296是一个相当大的数字。事实上,它太大了,无法放入32位整数中。这意味着,如果我们把它放在源代码中,一个不支持64位整数的编译器可能很难弄清楚如何处理它。这样写,编译器可以生成处理这个大数字所需的任何浮点指令。
当前代码错误的原因:
这个代码的变体似乎已经在互联网上流传了很长一段时间。最初(我认为)access_counter是使用rdtsc而不是rdtscp编写的。除了指出rdtsc不设置ecx,rdtscp设置ecx之外,我不会试图描述两者之间的区别(用谷歌搜索它们)。不管是谁把rdtsc改成了rdtscp,显然都不知道这一点,也没有调整内联汇编程序来反映它。尽管如此,你的代码可能工作得很好,但它可能会做一些奇怪的事情。要修复它,你可以做:
asm("rdtscp; movl %%edx,%0; movl %%eax,%1" // Read cycle counter
: "=r" (*hi), "=r" (*lo) // and move results to
: /* No input */ // the two outputs
: "%edx", "%eax", "%ecx");
虽然这是可行的,但它不是最佳的。寄存器是i386上一种宝贵而稀缺的资源。这个微小的碎片使用了其中的5个。稍作修改:
asm("rdtscp" // Read cycle counter
: "=d" (*hi), "=a" (*lo)
: /* No input */
: "%ecx");
现在我们少了2个汇编语句,并且只使用了3个寄存器。
但即使这样也不是我们能做的最好的。在编写这段代码以来的(可能很长的)时间里,gcc添加了对64位整数的支持和读取tsc的函数,所以你根本不需要使用asm:
unsigned int a;
unsigned long long result;
result = __builtin_ia32_rdtscp(&a);
"a"是在ecx中返回的(无用的?)值。函数调用需要它,但我们可以忽略返回的值。
因此,与其做这样的事情(我认为您现有的代码会这样做):
unsigned cyc_hi, cyc_lo;
access_counter(&cyc_hi, &cyc_lo);
// do something
double elapsed_time = get_counter(); // Find the difference between cyc_hi, cyc_lo and the current time
我们可以做到:
unsigned int a;
unsigned long long before, after;
before = __builtin_ia32_rdtscp(&a);
// do something
after = __builtin_ia32_rdtscp(&a);
unsigned long long elapsed_time = after - before;
它更短,不使用难以理解的汇编程序,更容易阅读、维护并生成尽可能好的代码。
但它确实需要相对较新版本的gcc。