Короткий ответ:
Эта строка превращает 64-битное целое число, которое хранится в виде двух 32-битных значений, в число с плавающей запятой.
Почему код просто не использует 64-битное целое число? Что ж, gcc уже давно поддерживает 64-битные числа, но, по-видимому, этот код предшествует этому. В этом случае единственный способ поддерживать такие большие числа — поместить их в число с плавающей запятой.
Длинный ответ:
Во-первых, вам нужно понять, как работает rdtscp. Когда эта ассемблерная инструкция вызывается, она делает 2 вещи:
1) Устанавливает ecx в IA32_TSC_AUX MSR. По моему опыту, это обычно просто означает, что ecx устанавливается на ноль. 2) Устанавливает edx:eax в текущее значение счетчика отметок времени процессора. Это означает, что младшие 64 бита счетчика идут в eax, а старшие 32 бита — в edx.
Имея это в виду, давайте посмотрим на код. При вызове из get_counter access_counter помещает edx в ncyc_hi и eax в ncyc_lo. Затем get_counter сделает:
lo = ncyc_lo - cyc_lo;
borrow = lo > ncyc_lo;
hi = ncyc_hi - cyc_hi - borrow;
Что это делает?
Поскольку время хранится в двух разных 32-битных числах, если мы хотим узнать, сколько времени прошло, нам нужно немного поработать, чтобы найти разницу между старым временем и новым. Когда это сделано, результат сохраняется (опять же, с использованием 2 32-битных чисел) в hi / lo.
Что, наконец, подводит нас к вашему вопросу.
result = (double) hi * (1 << 30) * 4 + lo;
Если бы мы могли использовать 64-битные целые числа, преобразование двух 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-битные целые числа, это не сработает. Хотя числа с плавающей запятой могут содержать такие большие значения, они не поддерживают сдвиг. Итак, нам нужно найти способ сдвинуть «привет» влево на 32 бита, без использования сдвига влево.
Итак, сдвиг влево на 1 на самом деле такой же, как умножение на 2. Сдвиг влево на 2 аналогичен умножению на 4. Сдвиг влево на [опущено...] Сдвиг влево на 32 аналогичен умножению на 4 294 967 296.
По удивительному совпадению 4 294 967 296 == (1 ‹‹ 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);
'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.
person
David Wohlferd
schedule
17.04.2016
unsigned int a; unsigned long long b = __builtin_ia32_rdtscp(&a);
? Если вы использовали 64-битное число для (очевидно, неопределенных?) cyc_lo и cyc_hi, это также упрощает вычитание нового и старого времени. - person David Wohlferd   schedule 17.04.2016