9

我正在研究使用 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 and的值eaxedx 为什么不删除 mov 并直接从and中读取 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 的结果)。

谢谢

4

2 回答 2

6

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

请参阅我的 GNU C 内联汇编指南/链接集合,以及标记 wiki 中的其他链接。(一般来说, 标签 wiki 也充满了 asm 的好东西。)


或者rdtsc具体来说,请参阅获取 CPU 周期数?对于__rdtsc()@Mysticial 的答案中内在的、良好的内联汇编。


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

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

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

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


并且回复:评论中的讨论:是cpuid的,是否可以序列化,确保rdtsc在 CPUID 之后才能开始执行以下指令。您可以在 RDTSC 之后放置另一个 CPUID,但这会增加测量开销,并且我认为在准确度/精度方面几乎为零。

LFENCE 是一种更便宜的替代品,可与 RDTSC 一起使用。指令参考手册条目记录了这样一个事实,即它不会让后面的指令开始执行,直到它和先前的指令退出(来自核心的无序部分的 ROB/RS)。请参阅加载和存储是唯一被重新排序的指令吗?,有关使用它的具体示例,请参阅clflush 通过 C 函数使缓存行无效。与真正的序列化指令不同cpuid,它不会刷新存储缓冲区。

(在最近没有启用 Spectre 缓解的 AMD CPUlfence上,甚至没有部分序列化,并且根据Agner Fog 的测试以每时钟 4次 运行。LFENCE是否在 AMD 处理器上序列化?

Margaret Bloom 挖出了这个有用的链接,这也证实了 LFENCE 根据 Intel 的 SDM 对 RDTSC 进行序列化,并且还有一些关于如何围绕 RDTSC 进行序列化的其他内容。

于 2016-08-17T17:28:08.437 回答
3

不,内联汇编中多余的 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));

同样,作者显然没有意识到改进的 asm 语句有一个更简单的版本,它使用 RDTSC 和 CPUID,正如您在帖子中所展示的那样。

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

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

于 2016-08-17T17:31:29.600 回答