使用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
答案 0 :(得分:25)
其他答案有效,但您可以使用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 ();
}
答案 1 :(得分:21)
在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
答案 2 :(得分:6)
更新: reposted and updated this answer 涉及一个更规范的问题。一旦我们找出要关闭所有相似的rdtsc
问题的重复目标,我可能会在某个时候删除它。
您不需要,也不应该为此使用嵌入式asm 。没有好处。编译器具有rdtsc
和rdtscp
的内置程序,并且(至少最近这些天)都定义了__rdtsc
内在函数(如果您包括正确的头文件)。 https://gcc.gnu.org/wiki/DontUseInlineAsm
不幸的是,MSVC与其他所有人不同意在非SIMD内部函数使用哪个标头。 (为此,Intel's intriniscs guide says #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位。参见the results on the Godbolt compiler explorer。
有关使用lfence
来提高rdtsc
的可重复性的更多信息,请参见@HadiBrais对clflush to invalidate cache line via C function的回答。
另请参见Is LFENCE serializing on AMD processors?(启用Spectre缓解功能的TL:DR是,否则内核未设置相关的MSR。)
rdtsc
计算的是参考周期,而不是CPU核心时钟周期无论turbo /节能模式如何,它都以固定的频率进行计数,因此,如果要进行每时钟一次的分析,请使用性能计数器。 rdtsc
与挂钟时间完全相关(系统时钟调整除外,因此基本上是steady_clock
)。它以CPU的额定频率(即广告的标贴频率)为刻度。
如果将其用于微基准测试,请在开始计时之前先包括一个预热时间,以确保您的CPU已经达到最大时钟速度。或更妙的是,使用可以访问硬件性能计数器的库,或者使用perf stat for part of program之类的技巧,如果您的定时区域足够长以至于您可以附加perf stat -p PID
。不过,您通常仍然希望避免在微基准测试期间发生CPU频率偏移。
也不能保证所有内核的TSC都是同步的。因此,如果您的线程在__rdtsc()
之间迁移到另一个CPU内核,则可能会有额外的偏差。 (不过,大多数操作系统都尝试同步所有内核的TSC。)如果您直接使用rdtsc
,则可能希望将程序或线程固定到某个内核,例如在Linux上使用taskset -c 0 ./myprogram
。
它至少与内联汇编所能做的一样好。
它的非内联版本像这样编译x86-64的MSVC:
unsigned __int64 readTSC(void) PROC ; readTSC
rdtsc
shl rdx, 32 ; 00000020H
or rax, rdx
ret 0
; return in RAX
对于在edx:eax
中返回64位整数的32位调用约定,它只是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个编译器都编写非常相似的代码。这是GCC的32位输出:
# 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
这是MSVC的x86-64输出(已应用名称分解)。 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
将上下半部分组合到一个不同的寄存器中。我想这是他们无法优化的固定顺序。
但是您自己以内联汇编编写它几乎没有更好的选择。如果您安排的时间间隔太短,只能保留32位结果,那么您将剥夺编译器去忽略EDX中高32位结果的机会。或者,如果编译器决定将开始时间存储到内存中,则可以只使用两个32位存储而不是shift / or / mov。如果您在计时过程中多了1个UOP,那么您最好用纯asm编写整个微基准测试。
答案 3 :(得分:5)
在使用gcc
的Linux上,我使用以下内容:
/* 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