简体   繁体   English

rdtscp、rdtsc 之间的区别:内存和 cpuid / rdtsc?

[英]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:所以我的问题是:

  • 1: Is my understanding of __volatile__ and "memory" correct? 1:我对__volatile__"memory"理解正确吗?
  • 2: Do the second two calls do the same thing? 2:后两个调用做同样的事情吗?
  • 3: Using "memory" looks much simpler than using another serializing instruction. 3:使用"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 语句中的volatilememory充当编译器屏障,但处理器仍然可以自由地重新排序指令。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM