Как считать тактовые циклы с RDTSC в GCC x86? [дубликат]
этот вопрос уже есть ответ здесь:
- получить счетчик циклов процессора? 4 ответы
С Visual Studio я могу прочитать счетчик тактового цикла от процессора, как показано ниже. Как мне сделать то же самое с GCC?
#ifdef _MSC_VER // Compiler: Microsoft Visual Studio
#ifdef _M_IX86 // Processor: x86
inline uint64_t clockCycleCount()
{
uint64_t c;
__asm {
cpuid // serialize processor
rdtsc // read time stamp counter
mov dword ptr [c + 0], eax
mov dword ptr [c + 4], edx
}
return c;
}
#elif defined(_M_X64) // Processor: x64
extern "C" unsigned __int64 __rdtsc();
#pragma intrinsic(__rdtsc)
inline uint64_t clockCycleCount()
{
return __rdtsc();
}
#endif
#endif
4 ответов
в последних версиях Linux gettimeofday будет включать наносекундные тайминги.
Если вы действительно хотите вызвать RDTSC, вы можете использовать следующую встроенную сборку:
http://www.mcs.anl.gov / ~kazutomo/rdtsc.html
#if defined(__i386__)
static __inline__ unsigned long long rdtsc(void)
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
#elif defined(__x86_64__)
static __inline__ unsigned long long rdtsc(void)
{
unsigned hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
#endif
другие ответы работают, но вы можете избежать встроенной сборки с помощью GCC __rdtsc
внутренней, доступной, включая x86intrin.h
.
определяется по адресу:gcc/config/i386/ia32intrin.h
:
/* rdtsc */
extern __inline unsigned long long
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
__rdtsc (void)
{
return __builtin_ia32_rdtsc ();
}
в Linux с gcc
, Я использую следующий:
/* define this somewhere */
#ifdef __i386
__inline__ uint64_t rdtsc() {
uint64_t x;
__asm__ volatile ("rdtsc" : "=A" (x));
return x;
}
#elif __amd64
__inline__ uint64_t rdtsc() {
uint64_t a, d;
__asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
return (d<<32) | a;
}
#endif
/* now, in your function, do the following */
uint64_t t;
t = rdtsc();
// ... the stuff that you want to time ...
t = rdtsc() - t;
// t now contains the number of cycles elapsed
обновление: репост и обновил этот ответ по более каноническому вопросу. Я, вероятно, удалю это в какой-то момент, как только мы разберемся, какой вопрос использовать в качестве дублирующей цели для закрытия всех подобных rdtsc
вопросы.
вам не нужно и не следует использовать встроенный asm для этого. Нет никакой пользы; компиляторы имеют встроенные модули для rdtsc
и rdtscp
и (по крайней мере в эти дни) все определения __rdtsc
intrinsic, если вы включаете правильные заголовки. https://gcc.gnu.org/wiki/DontUseInlineAsm
к сожалению, MSVC не согласен со всеми остальными о том, какой заголовок использовать для встроенных не SIMD. (руководство Intel intriniscs говорит #include <immintrin.h>
для этого, но с gcc и clang не-SIMD внутреннеприсущие в основном находятся в x86intrin.h
.)
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
// optional wrapper if you don't want to just use __rdtsc() everywhere
inline
unsigned long long readTSC() {
// _mm_lfence(); // optionally wait for earlier insns to retire before reading the clock
return __rdtsc();
// _mm_lfence(); // optionally block later instructions until rdtsc retires
}
компилируется со всеми 4 основными компиляторами: gcc/clang/ICC / MSVC, для 32 или 64-разрядных. посмотреть результаты в проводнике компилятора Godbolt.
подробнее об использовании lfence
для улучшения повторяемости rdtsc
, см. ответ @HadiBrais на clflush для аннулирования строки кэша через функцию C.
см. также сериализуется ли lfence на процессорах AMD? (TL: DR да с включенным смягчением спектра, в противном случае ядра оставляют соответствующий MSR unset.)
rdtsc
графы ссылка циклы, а не тактовые циклы ядра процессора
он подсчитывает на фиксированной частоте независимо от турбо / энергосбережения, поэтому, если вы хотите анализ uops-per-clock, используйте счетчики производительности. rdtsc
точно коррелирует со временем настенных часов (за исключением системных настроек часов, поэтому это в основном steady_clock
). Он тикает на номинальной частоте процессора, т. е. рекламируемой частоте наклейки.
если вы используете его для microbenchmarking, сначала включите период прогрева, чтобы убедиться, что ваш процессор уже на максимальной тактовой частоте, прежде чем начать синхронизацию. Или лучше использовать библиотеку, которая дает вам доступ к счетчикам производительности оборудования, или трюк, как perf stat для части программы если ваша временная область достаточно длинная, что вы можете прикрепить perf stat -p PID
. Обычно вы все равно хотите избежать частотных сдвигов процессора во время microbenchmark.
- std:: chrono:: часы, аппаратные часы и счетчик циклов
- становится процессора циклы с использованием RDTSC-почему значение RDTSC всегда увеличивается?
- потерянные циклы на Intel? Несоответствие между rdtsc и CPU_CLK_UNHALTED.REF_TSC
также не гарантируется, что TSCs всех ядер синхронизированы. Поэтому, если ваш поток мигрирует в другое ядро процессора между __rdtsc()
, может быть дополнительный перекос. (Большинство ОС пытаются синхронизировать TSC всех ядер.) Если вы используете rdtsc
напрямую, вы вероятно, вы хотите прикрепить свою программу или поток к ядру, например, с помощью taskset -c 0 ./myprogram
на Linux.
насколько хорош asm от использования внутреннего?
это, по крайней мере, так же хорошо, как все, что вы могли бы сделать с встроенным asm.
не встроенная версия компилирует MSVC для x86-64 следующим образом:
unsigned __int64 readTSC(void) PROC ; readTSC
rdtsc
shl rdx, 32 ; 00000020H
or rax, rdx
ret 0
; return in RAX
для 32-разрядных соглашений о вызовах, возвращающих 64-разрядные целые числа в edx:eax
, просто rdtsc
/ret
. Это не важно, ты всегда хочешь это рядные.
в тестовом вызывающем абоненте, который использует его дважды и вычитает интервал времени:
uint64_t time_something() {
uint64_t start = readTSC();
// even when empty, back-to-back __rdtsc() don't optimize away
return readTSC() - start;
}
все 4 компилятора делают довольно похожий код. Это 32-разрядный выход GCC:
# gcc8.2 -O3 -m32
time_something():
push ebx # save a call-preserved reg: 32-bit only has 3 scratch regs
rdtsc
mov ecx, eax
mov ebx, edx # start in ebx:ecx
# timed region (empty)
rdtsc
sub eax, ecx
sbb edx, ebx # edx:eax -= ebx:ecx
pop ebx
ret # return value in edx:eax
это выход x86-64 MSVC (с примененным именем-demangling). gcc / clang / ICC все испускают идентичный код.
# MSVC 19 2017 -Ox
unsigned __int64 time_something(void) PROC ; time_something
rdtsc
shl rdx, 32 ; high <<= 32
or rax, rdx
mov rcx, rax ; missed optimization: lea rcx, [rdx+rax]
; rcx = start
;; timed region (empty)
rdtsc
shl rdx, 32
or rax, rdx ; rax = end
sub rax, rcx ; end -= start
ret 0
unsigned __int64 time_something(void) ENDP ; time_something
все 4 компилятора используют or
+mov
вместо lea
совместить низкие и высокие половины в различный регистр. Я думаю это своего рода консервированная последовательность, которую они не могут оптимизировать.
но писать его в встроенном asm едва ли лучше. Вы лишите компилятор возможности игнорировать высокие 32 бита результата в EDX, если вы синхронизируете такой короткий интервал, что сохраняете только 32-битный результат. Или, если компилятор решит сохранить время начала в памяти, он может просто использовать два 32-битных хранилища вместо shift/или / mov. Если 1 дополнительный uop как часть вашего времени беспокоит вас, вам лучше напишите весь свой microbenchmark в чистом asm.