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

时间:2016-08-17 10:49:21

标签: c assembly benchmarking inline-assembly rdtsc

我正在研究使用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。为什么不删除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的结果。

由于

2 个答案:

答案 0 :(得分:6)

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

请参阅my GNU C inline asm guides / links collection以及代码wiki中的其他链接。 (标签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的误用相结合,这应该是英特尔编辑所抓住的,这让我怀疑它的可信度。本文。