Joh*_*åde 15 c c++ x86 gcc rdtsc
使用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
Run Code Online (Sandbox Code Playgroud)
Eva*_*haw 26
其他答案可行,但您可以通过使用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 ();
}
Run Code Online (Sandbox Code Playgroud)
And*_*zos 24
在最新版本的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
Run Code Online (Sandbox Code Playgroud)
Pet*_*des 12
更新:在一个更规范的问题上重新发布并更新了此答案。一旦我们选出哪个问题作为关闭所有类似rdtsc问题的重复目标,我可能会在某个时候删除它。
您不需要,也不应该为此使用嵌入式asm。没有好处。编译器已经内置插件的rdtsc和rdtscp,和(至少这些天)都定义了一个__rdtsc内在的,如果你有正确的头。 https://gcc.gnu.org/wiki/DontUseInlineAsm
不幸的是,MSVC与其他所有人不同意在非SIMD内部函数使用哪个标头。(英特尔的内部指南 #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
}
Run Code Online (Sandbox Code Playgroud)
可使用所有4种主要编译器进行编译:gcc / clang / ICC / MSVC,适用于32位或64位。在Godbolt编译器资源管理器上 查看结果。
有关lfence用于提高重复性的更多信息rdtsc,请参见@HadiBrais在clflush上的回答,以通过C函数使缓存行无效。
另请参阅LFENCE是否在AMD处理器上序列化?(启用Spectre缓解功能的TL:DR是,否则内核未设置相关的MSR。)
rdtsc计算参考周期,而不是CPU核心时钟周期无论涡轮增压或节能模式如何,它都以固定频率计数,因此,如果您希望每时钟分析一次,请使用性能计数器。 rdtsc与壁钟时间完全相关(系统时钟调整除外,因此基本上是steady_clock)。它在CPU的额定频率(即广告的标贴频率)上滴答作响。
如果您将其用于微基准测试,请在开始计时之前先包括一个预热时间,以确保您的CPU已经达到最大时钟速度。或者更好的方法是,使用一个可以访问硬件性能计数器的库,或者如果您的定时区域足够长以至于可以附加一个,那么可以使用诸如perf stat之类的技巧来处理部分程序perf stat -p PID。不过,您通常仍希望避免在微基准测试期间CPU频率偏移。
也不能保证所有内核的TSC都是同步的。因此,如果您的线程在之间迁移到另一个CPU内核__rdtsc(),则可能会有额外的偏差。(不过,大多数操作系统尝试同步所有内核的TSC。)如果rdtsc直接使用,则可能希望将程序或线程固定到内核,例如taskset -c 0 ./myprogram在Linux上。
至少与使用内联汇编可以执行的任何操作一样好。
它的非嵌入式版本可以像这样编译用于x86-64的MSVC:
unsigned __int64 readTSC(void) PROC ; readTSC
rdtsc
shl rdx, 32 ; 00000020H
or rax, rdx
ret 0
; return in RAX
Run Code Online (Sandbox Code Playgroud)
对于在其中返回64位整数的32位调用约定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;
}
Run Code Online (Sandbox Code Playgroud)
所有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
Run Code Online (Sandbox Code Playgroud)
这是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
Run Code Online (Sandbox Code Playgroud)
所有4个编译器都使用or+ mov而不是lea将低半部分和高半部分组合到不同的寄存器中。我想这是他们无法优化的固定顺序。
但是,自己以内联汇编编写它几乎没有更好的选择。如果您计时的时间间隔太短,只能保留32位结果,那么您将剥夺编译器去忽略EDX中高32位结果的机会。或者,如果编译器决定将开始时间存储到内存中,则可以只使用两个32位存储而不是shift / or / mov。如果您在计时过程中多了1个麻烦,您最好用纯asm编写整个微基准测试。
在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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
25950 次 |
| 最近记录: |