[英]Difference between rdtscp, rdtsc : memory and cpuid / rdtsc?
Assume we're trying to use the tsc for performance monitoring and we we want to prevent instruction reordering.假设我们正在尝试使用 tsc 进行性能监控,并且我们希望防止指令重新排序。
These are our options:这些是我们的选择:
1: rdtscp
is a serializing call. 1:
rdtscp
是一个序列化调用。 It prevents reordering around the call to rdtscp.它可以防止围绕对 rdtscp 的调用重新排序。
__asm__ __volatile__("rdtscp; " // serializing read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc variable
:
: "%rcx", "%rdx"); // rcx and rdx are clobbered
However, rdtscp
is only available on newer CPUs.但是,
rdtscp
仅在较新的 CPU 上可用。 So in this case we have to use rdtsc
.所以在这种情况下,我们必须使用
rdtsc
。 But rdtsc
is non-serializing, so using it alone will not prevent the CPU from reordering it.但是
rdtsc
是非序列化的,因此单独使用它不会阻止 CPU 对其重新排序。
So we can use either of these two options to prevent reordering:所以我们可以使用这两个选项中的任何一个来防止重新排序:
2: This is a call to cpuid
and then rdtsc
. 2:这是对
cpuid
的调用,然后是rdtsc
。 cpuid
is a serializing call. cpuid
是一个序列化调用。
volatile int dont_remove __attribute__((unused)); // volatile to stop optimizing
unsigned tmp;
__cpuid(0, tmp, tmp, tmp, tmp); // cpuid is a serialising call
dont_remove = tmp; // prevent optimizing out cpuid
__asm__ __volatile__("rdtsc; " // read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc
:
: "%rcx", "%rdx"); // rcx and rdx are clobbered
3: This is a call to rdtsc
with memory
in the clobber list, which prevents reordering 3:这是对
rdtsc
的调用,在 clobber 列表中有memory
,可防止重新排序
__asm__ __volatile__("rdtsc; " // read of tsc
"shl $32,%%rdx; " // shift higher 32 bits stored in rdx up
"or %%rdx,%%rax" // and or onto rax
: "=a"(tsc) // output to tsc
:
: "%rcx", "%rdx", "memory"); // rcx and rdx are clobbered
// memory to prevent reordering
My understanding for the 3rd option is as follows:我对第三个选项的理解如下:
Making the call __volatile__
prevents the optimizer from removing the asm or moving it across any instructions that could need the results (or change the inputs) of the asm.调用
__volatile__
可以防止优化器删除 asm 或将其移动到任何可能需要 asm 结果(或更改输入)的指令中。 However it could still move it with respect to unrelated operations.但是,它仍然可以针对不相关的操作移动它。 So
__volatile__
is not enough.所以
__volatile__
是不够的。
Tell the compiler memory is being clobbered: : "memory")
.告诉编译器内存正在被破坏:
: "memory")
。 The "memory"
clobber means that GCC cannot make any assumptions about memory contents remaining the same across the asm, and thus will not reorder around it. "memory"
破坏意味着 GCC 不能对整个 asm 中的内存内容保持不变做出任何假设,因此不会围绕它重新排序。
So my questions are:所以我的问题是:
__volatile__
and "memory"
correct? __volatile__
和"memory"
理解正确吗?"memory"
looks much simpler than using another serializing instruction. "memory"
看起来比使用另一个序列化指令简单得多。 Why would anyone use the 3rd option over the 2nd option?As mentioned in a comment, there's a difference between a compiler barrier and a processor barrier .正如评论中提到的,编译器屏障和处理器屏障之间存在差异。
volatile
and memory
in the asm statement act as a compiler barrier, but the processor is still free to reorder instructions. asm 语句中的
volatile
和memory
充当编译器屏障,但处理器仍然可以自由地重新排序指令。
Processor barrier are special instructions that must be explicitly given, eg rdtscp, cpuid
, memory fence instructions ( mfence, lfence,
...) etc.处理器屏障是必须明确给出的特殊指令,例如
rdtscp, cpuid
、内存栅栏指令( mfence, lfence,
...)等。
As an aside, while using cpuid
as a barrier before rdtsc
is common, it can also be very bad from a performance perspective, since virtual machine platforms often trap and emulate the cpuid
instruction in order to impose a common set of CPU features across multiple machines in a cluster (to ensure that live migration works). rdtsc
,虽然在rdtsc
之前使用cpuid
作为障碍很常见,但从性能角度来看,它也可能非常糟糕,因为虚拟机平台经常捕获和模拟cpuid
指令,以便在多台机器上强加一组通用的 CPU 功能在集群中(以确保实时迁移有效)。 Thus it's better to use one of the memory fence instructions.因此最好使用内存栅栏指令之一。
The Linux kernel uses mfence;rdtsc
on AMD platforms and lfence;rdtsc
on Intel. Linux内核使用
mfence;rdtsc
在AMD平台上和lfence;rdtsc
英特尔。 If you don't want to bother with distinguishing between these, mfence;rdtsc
works on both although it's slightly slower as mfence
is a stronger barrier than lfence
.如果您不想费心区分这些,
mfence;rdtsc
对两者都适用,尽管它稍微慢一些,因为mfence
是比lfence
更强的屏障。
Edit 2019-11-25 : As of Linux kernel 5.4, lfence is used to serialize rdtsc on both Intel and AMD.编辑 2019-11-25 :从 Linux 内核 5.4 开始,lfence 用于在 Intel 和 AMD 上序列化 rdtsc。 See this commit "x86: Remove X86_FEATURE_MFENCE_RDTSC": https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=be261ffce6f13229dad50f59c5e491f933d3167f
请参阅此提交“x86:删除 X86_FEATURE_MFENCE_RDTSC”: https ://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/ ? id = be261ffce6f13229dad50f59c5e491f933d3167f
you can use it like shown below:你可以像下图那样使用它:
asm volatile (
"CPUID\n\t"/*serialize*/
"RDTSC\n\t"/*read the clock*/
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r"
(cycles_low):: "%rax", "%rbx", "%rcx", "%rdx");
/*
Call the function to benchmark
*/
asm volatile (
"RDTSCP\n\t"/*read the clock*/
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t"
"CPUID\n\t": "=r" (cycles_high1), "=r"
(cycles_low1):: "%rax", "%rbx", "%rcx", "%rdx");
In the code above, the first CPUID call implements a barrier to avoid out-of-order execution of the instructions above and below the RDTSC instruction.在上面的代码中,第一个 CPUID 调用实现了一个屏障,以避免无序执行 RDTSC 指令上方和下方的指令。 With this method we avoid to call a CPUID instruction in between the reads of the real-time registers
使用这种方法,我们避免在读取实时寄存器之间调用 CPUID 指令
The first RDTSC then reads the timestamp register and the value is stored in memory.然后第一个 RDTSC 读取时间戳寄存器并将值存储在内存中。 Then the code that we want to measure is executed.
然后执行我们要测量的代码。 The RDTSCP instruction reads the timestamp register for the second time and guarantees that the execution of all the code we wanted to measure is completed.
RDTSCP指令第二次读取时间戳寄存器,保证我们要测量的所有代码都执行完毕。 The two “mov” instructions coming afterwards store the edx and eax registers values into memory.
随后出现的两条“mov”指令将 edx 和 eax 寄存器值存储到内存中。 Finally a CPUID call guarantees that a barrier is implemented again so that it is impossible that any instruction coming afterwards is executed before CPUID itself.
最后,CPUID 调用保证了屏障再次实现,因此之后的任何指令都不可能在 CPUID 本身之前执行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.