英特尔的时间戳读取asm代码示例是否使用了两个以上的寄存器?

我正在研究使用x86 CPU中的时间戳寄存器(TSR)来测量基准性能。 它是一个有用的寄存器,因为它以单调时间单位测量,不受时钟速度变化的影响。 很酷。

这是一份英特尔文档,显示了使用TSR进行可靠基准测试的asm片段,包括使用cpuid进行管道同步。 见第16页:

http://www.intel.com/content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html

要读取开始时间,它说(我注释了一下):

__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 

我想知道为什么使用临时寄存器来获取edxeax的值。 为什么不删除mov并从edxeax读取TSR值? 像这样:

 __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的结果)。

谢谢

你是对的,这个例子很笨重。 通常,如果mov是inline-asm语句中的第一个或最后一个指令,那么你做错了 ,并且应该使用约束来告诉编译器你想要输入的位置,或输出的位置。

请参阅我的GNU C内联asm指南/链接集合 ,以及内联汇编标记wiki中的其他链接。 (对于asm来说, x86标签wiki也是很好的东西。)


或者对于rdtsc具体请参阅获取CPU周期数? 对于__rdtsc()内在函数,以及@ Mysticial的答案中的良好内联asm。


它以单调时间单位测量,不受时钟速度变化的影响。

是的,在过去10年左右的CPU上。

对于分析,在核心时钟周期中有时间而不是挂钟时间通常更有用, 因此您的微基准测试结果不依赖于省电/ turbo。 性能计数器可以做到这一点以及更多。

尽管如此,如果实时是您想要的, rdtsc是获得它的最低开销方式。


并且re:在评论中讨论:是的cpuid是序列化的,确保rdtsc和以下指令在CPUID之后才能开始执行。 您可以在RDTSC之后添加另一个CPUID,但这会增加测量开销,并且我认为在准确度/精度方面提供接近零的增益。

LFENCE是一种更便宜的替代品,对RDTSC很有用。 指令参考手册条目记录了以下事实:它不允许稍后的指令开始执行,直到它和先前的指令已经退出 (来自核心的无序部分中的ROB / RS)。 请参阅是否加载并存储重新排序的唯一指令? ,有关使用它的具体示例,请参阅clflush以通过C函数使高速缓存行无效 。 与cpuid等真正的序列化指令不同,它不会刷新存储缓冲区。

(在没有启用Spectre缓解的最新AMD CPU上,根据Agner Fog的测试 , lfence甚至不是部分序列化,每个时钟运行4次.LFENCE 是否在AMD处理器上进行序列化? )

玛格丽特布鲁姆挖出了这个有用的链接 ,这也确认了LFENCE根据英特尔的SDM序列化RDTSC,还有一些关于如何围绕RDTSC进行序列化的事情。

不,内联汇编中的冗余MOV指令似乎没有充分的理由。 本文首先介绍内联汇编,声明如下:

 asm volatile ( "RDTSC\n\t" "mov %%edx, %0\n\t" "mov %%eax, %1\n\t": "=r" (cycles_high1), "=r" (cycles_low1)); 

这有一个明显的问题,它没有告诉编译器ETS和EDX已被RDTSC指令修改。 该文件指出了这个错误,并使用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结合使用,正如您在post中所展示的那样。

请注意,该论文的作者反复滥用术语“IA64”来引用64位x86指令集和体系结构(不同地称为x86_64,AMD64和Intel 64)。 IA-64架构实际上是完全不同的东西,它是英特尔Itaninum CPU使用的架构。 它没有EAX或RAX寄存器,也没有RDTSC指令。

虽然作者内联汇编比它需要的更复杂并不重要,但这一事实与IA64的滥用相结合,这应该是英特尔编辑所抓住的,这让我怀疑这篇论文的可信度。