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