rdtscp,rdtsc之间的区别:内存和cpuid / rdtsc?
Difference between rdtscp, rdtsc : memory and cpuid / rdtsc?
假设我们尝试使用 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
的调用,然后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:这是对 clobber 列表中memory
的rdtsc
的调用,这会阻止重新排序
__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")
."memory"
clobber 意味着 GCC 不能对内存内容在整个 asm 中保持不变做出任何假设,因此不会围绕它重新排序。
所以我的问题是:
- 1:我对
__volatile__
和"memory"
的理解是否正确? - 2:后两个调用是否执行相同的操作?
- 3:使用
"memory"
看起来比使用另一个序列化指令简单得多。为什么有人会使用第三个选项而不是第二个选项?
如注释中所述,编译器屏障和处理器屏障之间存在差异。 ASM 语句中的volatile
和memory
充当编译器屏障,但处理器仍然可以自由地对指令重新排序。
处理器屏障是必须明确给出的特殊指令,例如rdtscp, cpuid
、记忆围栏指令(mfence, lfence,
...)等。
顺便说一句,虽然在rdtsc
之前使用cpuid
作为屏障很常见,但从性能的角度来看,它也可能是非常糟糕的,因为虚拟机平台经常捕获和模拟cpuid
指令,以便在集群中的多台机器上强加一组通用的 CPU 功能(以确保实时迁移正常工作)。因此,最好使用内存围栏指令之一。
Linux 内核在 AMD 平台上使用mfence;rdtsc
,在英特尔上使用lfence;rdtsc
。如果您不想费心区分这些,mfence;rdtsc
两者都有效,尽管它稍微慢一些,因为mfence
是比lfence
更强的障碍。
编辑2019-11-25:从Linux内核5.4开始,lfence用于在英特尔和AMD上序列化rdtsc。请参阅此提交"x86:删除X86_FEATURE_MFENCE_RDTSC":https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=be261ffce6f13229dad50f59c5e491f933d3167f
你可以像下面这样使用它:
asm volatile (
"CPUIDnt"/*serialize*/
"RDTSCnt"/*read the clock*/
"mov %%edx, %0nt"
"mov %%eax, %1nt": "=r" (cycles_high), "=r"
(cycles_low):: "%rax", "%rbx", "%rcx", "%rdx");
/*
Call the function to benchmark
*/
asm volatile (
"RDTSCPnt"/*read the clock*/
"mov %%edx, %0nt"
"mov %%eax, %1nt"
"CPUIDnt": "=r" (cycles_high1), "=r"
(cycles_low1):: "%rax", "%rbx", "%rcx", "%rdx");
在上面的代码中,第一个 CPUID 调用实现了一个屏障,以避免 RDTSC 指令上方和下方的指令无序执行。使用这种方法,我们避免在读取实时寄存器之间调用CPUID指令
然后,第一个RDTSC读取时间戳寄存器,并将值存储在 记忆。然后执行我们要测量的代码。RDTSCP 指令第二次读取时间戳寄存器,并保证我们想要测量的所有代码的执行都已完成。随后出现的两个"mov"指令将edx和eax寄存值存储到内存中。最后,CPUID 调用保证再次实现屏障,因此之后的任何指令都不可能在 CPUID 本身之前执行。
- 使用rdtsc进行基准测试的缺点是什么
- 如何在Powerpc中使用内联程序集获取CPUID?
- 使用“rdtsc”:错误 C2065
- 如何使用 CPUID 作为序列化指令
- 解决rdtsc的解决方案执行
- 使用rdtsc计算系统时间
- 无法使用 OpenVino 推理编译 OpenCV,无法读取 cpuid.txt
- 在x86_64平台上是否需要 rdtsc 的 mfence?
- i5-2500k 上的 cpuid 指令:未设置 MMX、SSE、SSE2 位
- X86体系结构中的CPUID
- rdtscp,rdtsc之间的区别:内存和cpuid / rdtsc?
- 如何以编程方式访问c/c++a-la-rdtsc中的性能计数器
- CPUID品牌字符串损坏
- 如果在英特尔 CPU 上关闭"SpeedStep Technology",RDTSC() 是否准确?
- RDTSC 在 Ubuntu 中很慢
- VisualStudio 2010 Express-C++上的RDTSC不支持default-int
- cpp linux: about rdtsc
- 在ARM中有与rdtsc等价的指令吗?
- 函数 __asm__ __volatile__( "rdtsc" );
- RDTSC管理费用的差异