rdtscp,rdtsc:memory和cpuid / rdtsc之间的区别?

假设我们正在尝试使用tsc进行性能监视,并且我们希望阻止指令重新sorting。

这些是我们的select:

1: rdtscp是一个序列化调用。 它阻止了对rdtscp调用的重新sorting。

 __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 

但是, rdtscp仅适用于较新的CPU。 所以在这种情况下,我们必须使用rdtsc 。 但rdtsc是非序列化的,所以单独使用它不会阻止CPU对其进行重新sorting。

所以我们可以使用这两个选项来防止重新sorting:

2:这是对cpuid然后rdtsc的调用。 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:这是一个调用rdtsc与clobber列表中的memory ,防止重新sorting

 __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 

我对第三种select的理解如下:

通过调用__volatile__可以防止优化器移除或移动可能需要结果(或更改input)的指令。 然而,它仍然可以移动它与无关的操作。 所以__volatile__是不够的。

告诉编译器内存被破坏: "memory")"memory"破坏意味着GCC不能对内存内容保留相同的假设,因此不会对其重新sorting。

所以我的问题是:

  • 1:我对__volatile__"memory"理解是否正确?
  • 2:接下来的两个电话做同样的事情吗?
  • 3:使用"memory"看起来比使用另一个序列化指令简单得多。 为什么有人会使用第二个选项的第三个选项?

正如评论中所提到的, 编译器障碍处理器障碍之间存在差异。 volatile语句和asm语句中的memory充当编译器障碍,但处理器仍然可以自由地重新sorting指令。

处理器屏障是必须明确给出的特殊指令,例如rdtscp, cpuid ,内存围栏指令( mfence, lfence, …)等。

另外,在rdtsc常用之前使用cpuid作为屏障的同时,从性能的angular度来看,它也可能非常糟糕,因为虚拟机平台通常会捕获和模拟cpuid指令,以便在多个机器上强加一组共同的CPU特性在一个集群中(以确保实时迁移工作)。 因此,最好使用内存围栏指令之一。

Linux内核在AMD平台上使用mfence;rdtsc ,在Intel上使用mfence;rdtsc 。 如果你不想区分这两者, mfence;rdtsc就可以同时工作,尽pipe它稍微慢一点,因为mfence是比lfence更强的障碍。

你可以使用它如下所示:

 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"); 

在上面的代码中,第一个CPUID调用实现了一个屏障,以避免RDTSC指令上下的乱序执行。 采用这种方法,我们避免在读取实时寄存器之间调用CPUID指令

第一个RDTSC然后读取时间戳寄存器,并将该值存储在内存中。 然后我们要测量的代码被执行。 RDTSCP指令第二次读取时间戳寄存器,并保证我们要测量的所有代码的执行完成。 之后的两个“mov”指令将edx和eax寄存器值存储到内存中。 最后一个CPUID调用保证再次执行一个屏障,以便在CPUID本身之前执行任何后续的指令是不可能的。