假设我们试图使用TSC进行性能监控,并且我们希望防止指令重新排序.

以下是我们的 Select :

1: 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

但是,rdtscp只在较新的CPU上可用.所以在这种情况下,我们必须使用rdtsc.但是rdtsc是非序列化的,所以单独使用它不会阻止CPU对其重新排序.

因此,我们可以使用这两个选项之一来防止重新排序:

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,这会阻止重新排序

__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__防止优化器移除ASM或在可能需要ASM的结果(或改变输入)的任何指令之间移动它.但是,对于不相关的操作,它仍然可以移动它.所以__volatile__是不够的.

告诉编译器内存正在被阻塞:: "memory")."memory" clobber意味着GCC不能对asm中保持不变的内存内容做出任何假设,因此不会对其重新排序.

所以我的问题是:

  • 1:我对__volatile__"memory"的理解正确吗?
  • 他说:第二个电话是不是做了同样的事情?
  • 3:使用"memory"看起来比使用另一条序列化指令简单得多.为什么有人会使用第三种 Select 而不是第二种 Select ?

推荐答案

正如 comments 中提到的,compiler barrierprocessor barrier是有区别的.ASM语句中的第volatilememory充当编译器屏障,但是处理器仍然可以自由地对指令进行重新排序.

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

另一方面,虽然在rdtsc之前使用cpuid作为屏障很常见,但从性能Angular 来看,它也可能非常糟糕,因为虚拟机平台经常捕获并模拟cpuid指令,以便在集群中的多台机器上实施一组通用的CPU功能(以确保实时迁移工作).因此,最好使用一个内存围栏指令.

Linux内核在AMD平台上使用mfence;rdtsc,在英特尔平台上使用lfence;rdtsc.如果你不想费心go 区分它们,mfence;rdtsc对两者都有效,尽管它稍微慢一些,因为mfence的屏障比lfence强.

Edit 2019-11-25:从Linux内核5.4开始,lfence用于在Intel和AMD上序列化rdtsc.参见提交"x86:删除x86_功能_MFENCE_RDTSC":https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=be261ffce6f13229dad50f59c5e491f933d3167f

C++相关问答推荐

问关于C中的指针和数组

位屏蔽对于无符号转换是强制的吗?

以c格式打印时间戳

malloc实现:判断正确的分配对齐

GCC引发不明确的诊断消息

如何将字符串传递给函数并返回在C中更改的相同字符串?

我可以在C中声明不同长度数组的数组而不带变量名吗?

为什么即使在强制转换时,此代码也会溢出?

使用双指针动态分配和初始化2D数组

我在这里正确地解释了C操作顺序吗?

试图从CSV文件中获取双精度值,但在C++中始终为空

为什么数组的最后一个元素丢失了?

将返回的char*设置为S在函数中定义的字符串文字可能会产生什么问题?

Wcstok导致分段故障

我不知道为什么它不能正常工作,我用了get()和fget(),结果是一样的

如何逐位读取二进制文件?

如何在MSVC中使用intSafe.h函数?

Malloc和对齐

哪些C++功能可以在外部C块中使用

为什么需要struct in_addr