我在这一特定行中感到困惑 - >
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: %.0f\n", result);
}
return result;
}
我无法理解的是,为什么hi乘以2 ^ 30然后4?然后低添加到它?有人请解释这行代码中发生的事情。我确实知道hi和low包含的内容。
答案 0 :(得分:1)
答案简短:
该行将64位整数转换为2个32位值,并将其存储为浮点数。
为什么代码不使用64位整数?好吧,gcc长期以来一直支持64位数字,但可能这个代码早于此。在这种情况下,支持大数字的唯一方法是将它们放入浮点数。
答案很长:
首先,您需要了解rdtscp的工作原理。调用此汇编程序指令时,它会执行两项操作:
1)将ecx设置为IA32_TSC_AUX MSR。根据我的经验,这通常意味着ecx设置为零。 2)将edx:eax设置为处理器时间戳计数器的当前值。这意味着计数器的低64位进入eax,高32位进入edx。
考虑到这一点,让我们看看代码。当从get_counter调用时,access_counter将把edx放入&#39; ncyc_hi&#39;和“ncyc_lo”中的eax。&#39;然后get_counter会这样做:
lo = ncyc_lo - cyc_lo;
borrow = lo > ncyc_lo;
hi = ncyc_hi - cyc_hi - borrow;
这是做什么的?
由于时间存储在2个不同的32位数字中,如果我们想知道已经过了多少时间,我们需要做一些工作来找出旧时间和新时间之间的差异。完成后,结果将被存储(再次,使用2个32位数字)hi / lo。
这最终将我们带到了您的问题。
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位整数,那么它就不会起作用。虽然浮点数可以保持较大的值,但它们不支持移位。因此,我们需要找到一种方法来转移&#39; hi&#39;剩下32位,没有使用左移。
好吧那么,向左移动1实际上与乘以2相同。向左移动2与乘以4相同向左移动[省略...]向左移动32与乘以相同4294967296。
令人惊讶的巧合,4,294,967,296 ==(1 <&lt; 30)* 4.
那么为什么要以复杂的方式写呢?那么,4,294,967,296是一个相当大的数字。事实上,它太大了,不适合32位整数。这意味着如果我们将它放在我们的源代码中,那么不支持64位整数的编译器可能无法确定如何处理它。写得像这样,编译器可以生成可能需要处理的任何浮点指令。
为什么当前代码错误:
看起来这段代码的变体已经在互联网上流传了很长时间。最初(我假设)access_counter是使用rdtsc而不是rdtscp编写的。我不会试图描述两者之间的区别(谷歌他们),除了指出rdtsc没有设置ecx,而rdtscp确实如此。谁将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);
&#39;一个&#39;是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。