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

Is Intel's timestamp reading asm code example using two more registers than are necessary?

我正在研究使用 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

我想知道为什么使用临时寄存器来获取 edx 的值 和 eax。为什么不删除 movs 并直接从 edx 中读取 TSR 值 eax?像这样:

__asm volatile(                                                             
    "cpuid\n\t"
    "rdtsc\n\t"
    //
    : "=d" (cycles_high), "=a" (cycles_low) // outputs
    :                                       // inputs
    : "%rbx", "%rcx");                      // clobber     

通过这样做,你节省了两个寄存器,减少了 C 编译器需要溢出。

我说的对吗?还是那些 MOV 具有某种战略意义?

(我同意你确实需要临时寄存器来读取 stop 时间,因为 在那种情况下,指令的顺序是相反的:你有 rdtscp,...,cpuid。 cpuid指令破坏了rdtscp的结果)。

谢谢

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

参见 , and other links in the tag wiki. (The 标签 wiki 也有很多关于 asm 的好东西。)


或者对于 rdtsc 具体来说,请参阅 Get CPU cycle count? 以了解 @Mysticial 的回答中的 __rdtsc() 内在和良好的内联 asm。


it measures in a monotonic unit of time which is immune to the clock speed changing.

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

对于分析,以核心时钟周期为单位的时间通常更有用,而不是挂钟时间,性能计数器可以做到这一点以及更多。

不过,如果您想要实时,rdtsc 是实现它的最低开销方式。


回复:评论中的讨论:是的 cpuid 用于序列化,确保 rdtsc 和后续指令在 CPUID 之后才能开始执行。您可以在 RDTSC 之后放置另一个 CPUID,但这会增加测量开销,我认为准确度/精度的增益接近于零。

LFENCE 是一种更便宜的替代方案,可用于 RDTSC。 instruction ref manual entry documents the fact that it doesn't let later instructions start executing until it and previous instructions have retired (from the ROB/RS in the out-of-order part of the core). See Are loads and stores the only instructions that gets reordered?, and for a specific example of using it, see 。与 cpuid 等真正的序列化指令不同,它不会刷新存储缓冲区。

(在最近未启用 Spectre 缓解措施的 AMD CPU 上,lfence 甚至没有部分序列化,并且根据 Agner Fog's testing. Is LFENCE serializing on AMD processors? 以每时钟 4 次的速度运行)

Margaret Bloom 挖出 this useful link,这也证实了 LFENCE 根据 Intel 的 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));

这有一个明显的问题,即它没有告诉编译器 EAX 和 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));

同样,正如您在 post.

中所展示的那样,作者显然没有意识到改进后的 asm 语句有一个更简单的版本,该版本将 RDTSC 与 CPUID 结合使用

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

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