我正在研究使用x86 CPU中的时间戳寄存器(TSR)来测量基准性能。它是一个有用的寄存器,因为它以一个不受时钟影响的单调时间单位进行测量 变速。很酷。
这是一份英特尔文档,显示了使用TSR进行可靠基准测试的asm片段,包括使用cpuid进行管道同步。见第16页:
要读取开始时间,它会说(我注释了一下):
__asm volatile (
"cpuid\n\t" // writes e[abcd]x
"rdtsc\n\t" // writes edx, eax
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t"
//
:"=r" (cycles_high), "=r" (cycles_low) // outputs
: // inputs
:"%rax", "%rbx", "%rcx", "%rdx"); // clobber
我想知道为什么使用临时寄存器来获取edx
的值
和eax
。为什么不删除mov并从edx
中读取TSR值
和eax
?像这样:
__asm volatile(
"cpuid\n\t"
"rdtsc\n\t"
//
: "=d" (cycles_high), "=a" (cycles_low) // outputs
: // inputs
: "%rbx", "%rcx"); // clobber
通过执行此操作,您可以保存两个寄存器,从而降低C的可能性 编译器需要溢出。
我是对的吗?或者那些MOV在某种程度上是战略性的?
(我同意你需要暂存寄存器来读取停止时间,如 在那种情况下,指令的顺序是相反的:你有 rdtscp,...,cpuid。 cpuid指令破坏了rdtscp的结果。
由于
答案 0 :(得分:6)
你是对的,这个例子很笨重。 通常如果mov
是inline-asm语句中的第一个或最后一个指令,那么你做错了,并且应该使用约束来告诉编译器你想要输入的位置,或输出的位置。
请参阅my GNU C inline asm guides / links collection以及inline-assembly代码wiki中的其他链接。 (x86标签wiki对于asm来说也是很好的东西。)
或者对于rdtsc
具体而言,请参阅Get CPU cycle count? 了解__rdtsc()
内在因素,以及@ Mysticial答案中的良好内联作用。
它以单调时间单位测量,不受时钟速度变化的影响。
是的,在过去10年左右的CPU上。
对于分析,在核心时钟周期中使用时间通常更有用,而不是挂钟时间,so your microbenchmark results don't depend on power-saving / turbo.性能计数器可以做到这一点以及更多。
尽管如此,如果您想要实时,rdtsc
是获得它的最低开销方式。
重新:评论中的讨论:是cpuid
是序列化,确保rdtsc
及以下指令在CPUID之后才能开始执行。您可以在RDTSC之后添加另一个CPUID,但这会增加测量开销,我认为在准确度/精度方面会给出接近零的增益。
LFENCE是一种更便宜的替代方案,对RDTSC很有用。 instruction ref manual entry记录了以下事实:它不允许后来的指令开始执行,直到它和先前的指令退役(来自核心的无序部分中的ROB / RS) )。请参阅Are loads and stores the only instructions that gets reordered?,有关使用它的具体示例,请参阅clflush to invalidate cache line via C function。与cpuid
之类的真正序列化指令不同,它不会刷新存储缓冲区。
(在最近没有启用Spectre缓解的AMD CPU上,lfence
甚至没有部分序列化,并且根据Agner Fog's testing每个时钟运行4次。Is LFENCE serializing on AMD processors?)
Margaret Bloom挖出this useful link,这也证实了LFENCE根据英特尔的SDM序列化了RDTSC,还有其他一些关于如何围绕RDTSC进行序列化的事情。
答案 1 :(得分:3)
不,内联汇编中的冗余MOV指令似乎没有充分的理由。本文首先介绍内联汇编,其中包含以下声明:
asm volatile (
"RDTSC\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t": "=r" (cycles_high1), "=r" (cycles_low1));
这有一个明显的问题,即它没有告诉编译器RDESC指令修改了EAX和EDX。该文件指出了这个错误,并使用clobbers纠正它:
asm volatile ("RDTSC\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r" (cycles_low)::
“%eax”, “%edx”)
除了纠正前一个例子中的错误之外,没有其他理由以这种方式编写。看来该论文的作者根本不知道它可以更简单地写成:
asm volatile ("RDTSC\n\t"
: "=d" (cycles_high), "=a" (cycles_low));
同样地,作者显然没有意识到改进的asm语句的更简单版本将RDTSC与CPUID结合使用,正如您在帖子中所展示的那样。
请注意,本文作者反复滥用术语“IA64”来引用64位x86指令集和体系结构(不同地称为x86_64,AMD64和Intel 64)。 IA-64架构实际上是完全不同的,它是Intel的Itaninum CPU使用的架构。它没有EAX或RAX寄存器,也没有RDTSC指令。
虽然作者内联汇编比它需要的更复杂并不重要,但这个事实与IA64的误用相结合,这应该是英特尔编辑所抓住的,这让我怀疑它的可信度。本文。