假设我们尝试使用 tsc 进行性能监控,并且我们希望防止指令重新排序。
这些是我们的选择:
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
. But 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
with 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
我对第三个选项的理解如下:
拨打电话__volatile__
防止优化器删除 asm 或将其移动到任何可能需要 asm 结果(或更改输入)的指令。然而,对于不相关的操作,它仍然可以移动它。所以__volatile__
是不足够的。
告诉编译器内存正在被破坏:: "memory")
. The "memory"
clobber 意味着 GCC 无法对整个 asm 中内存内容保持不变做出任何假设,因此不会围绕它重新排序。
所以我的问题是:
- 1:我的理解是
__volatile__
and "memory"
正确的?
- 2:后两个调用做同样的事情吗?
- 3:使用
"memory"
看起来比使用另一个序列化指令简单得多。为什么有人会使用第三个选项而不是第二个选项?