Performance x86 rep指令在现代(流水线/超标量)处理器上的性能

Performance x86 rep指令在现代(流水线/超标量)处理器上的性能,performance,assembly,x86,pipeline,Performance,Assembly,X86,Pipeline,我最近一直在写x86汇编(为了好玩),想知道rep前缀字符串指令在现代处理器上是否真的有性能优势,或者它们的实现只是为了向后兼容 我能理解为什么英特尔最初会在处理器一次只运行一条指令时实现rep指令,但现在使用它们有好处吗 有了编译成更多指令的循环,就有更多指令需要填充管道和/或无序发布。现代处理器是为优化这些rep前缀指令而设计的,还是在现代代码中很少使用rep指令,以至于它们对制造商不重要?在AMD和Intel的优化指南中,类似的问题都有很大的篇幅。在这方面给出的建议的有效性有一个“半衰期”

我最近一直在写x86汇编(为了好玩),想知道rep前缀字符串指令在现代处理器上是否真的有性能优势,或者它们的实现只是为了向后兼容

我能理解为什么英特尔最初会在处理器一次只运行一条指令时实现rep指令,但现在使用它们有好处吗


有了编译成更多指令的循环,就有更多指令需要填充管道和/或无序发布。现代处理器是为优化这些rep前缀指令而设计的,还是在现代代码中很少使用rep指令,以至于它们对制造商不重要?

在AMD和Intel的优化指南中,类似的问题都有很大的篇幅。在这方面给出的建议的有效性有一个“半衰期”——不同的CPU代表现不同,例如:

  • ,第167页第8.3节:
    在执行字符串操作时,尤其是在复制内存块时,避免使用REP前缀
  • ,第148页第9.3节:
    执行字符串操作时,请谨慎使用REP前缀
表7-2给出了各种块复制技术(包括
rep stosd
)的性能比较图。内存复制例程的相对性能,第7-37f页,针对不同的CPU,同样,在一个CPU上最快的可能在其他CPU上不最快

对于许多情况,最新的x86 CPU(具有“字符串”SSE4.2操作)可以通过SIMD单元执行字符串操作,请参阅


要跟进所有这些(和/或当事情不可避免地再次发生变化时保持更新),请阅读。

除了FrankH的优秀答案;我想指出,哪种方法最好还取决于字符串的长度、对齐方式以及长度是固定的还是可变的

对于小字符串(可能最多16字节),使用简单指令手动执行可能更快,因为它避免了更复杂技术的设置成本(对于固定大小的字符串,可以轻松展开)。对于中等大小的字符串(可能从16字节到4kib),类似于“REP MOVSD”(如果可能出现不对齐,则抛出一些“MOVSB”指令)的东西可能是最好的

对于任何比这更大的问题,有些人会倾向于使用SSE/AVX和预取等。更好的办法是修复调用方,这样就不需要首先进行复制(或strlen()或其他什么)。如果你足够努力,你几乎总能找到办法。注意:还要非常小心“假定”的fast mempcy()例程——通常它们是在大量字符串上测试的,而不是在更可能的小/小/中字符串上测试的


还要注意的是(为了优化而非方便),由于所有这些差异(可能的长度、对齐方式、固定或可变大小、CPU类型等),在所有不同的情况下使用一个多用途“memcpy()”的想法是短视的。

因为没有人给你任何数字,我会给你一些我通过测试我的垃圾收集器发现的东西,它非常内存重。我要复制的对象长度为60%16字节,其余30%为500-8000字节左右

  • 前提条件:
    dst
    src
    n
    都是8的倍数
  • 处理器:AMD Phenom(tm)II X6 1090T处理器64位/linux
以下是我的三个
memcpy
变体:

手动编码的while循环:

if (n == 16) {
    *dst++ = *src++;
    *dst++ = *src++;
} else {
    size_t n_ptrs = n / sizeof(ptr);
    ptr *end = dst + n_ptrs;
    while (dst < end) {
        *dst++ = *src++;
    }
}
时间:103.22%

rep movsq

if (n == 16) {
    *dst++ = *src++;
    *dst++ = *src++;
} else {
    size_t n_ptrs = n / sizeof(ptr);
    asm volatile("cld\n\t"
                 "rep ; movsq"
                 : "=D" (dst), "=S" (src)
                 : "c" (n_ptrs), "D" (dst), "S" (src)
                 : "memory");
}
时间:100.00%


req movsq
以微弱优势获胜

我已经5年没有研究过这个了,但当时我的个人经验是,至少rep movsd和rep stosd比简单的循环快,而一些扫描变体则不然。不过,自那以后,情况可能发生了重大变化。在不同的处理器上进行测试,然后自己看看。谢谢大家的投入。亚历克斯:我可能最终会的,但我没有很多不同的程序可以尝试,所以它只能在一个真正的程序和一个没有管道的模拟器上进行。另外,我很懒,如果别人已经做了,我宁愿不做那项工作相关:,NT存储与常规存储,以及关于单个核心如何不能始终饱和内存带宽的内容(请参阅答案中的“延迟限制平台”)。还比较了
rep movs
/
stos
与vector loops.Ack。《优化指南》(Intel/AMD以及Agner Fog的材料和许多其他材料)也提到了这些东西;在许多情况下,战略是:1。对于短字符串,内联基本指令2。对于中等大小,大操作数大小
rep mov
3。对于已知的大数据块,请使用SIMD单元。并且始终对数据进行测试,因为如果大多数字符串都是IIRC
REP MOVSD
在现代硬件上通常比
REP MOVSB
慢得多,那么“极快VVX”性能就会崩溃。可能是因为现代CPU只针对
REP-MOVSB
进行了特殊优化,因为它的使用频率远远高于
REP-MOVSD
@PaulGroke:可能有几个CPU的
REP-MOVSB
优于
REP-MOVSD
,但大多数人也为
rep movsd
/
movsq
实现了所有ERMSB魔法。在IvyBridge增强的rep-movsb功能之前,
rep-movsb
通常在英特尔CPU上更差。请参阅,这是一个非常好的答案,其中包含大量关于x86内存带宽的详细信息。RCX寄存器也由REP MOV更改。我们如何修复上述代码以声明对CX的更改?(声明将其设置为0?@CecilWard:has save inline asm for
rsp movsb
)。另一个选项是使用
“+c”(n)
作为输入/输出操作数。如果你从来没有读过那本书
if (n == 16) {
    *dst++ = *src++;
    *dst++ = *src++;
} else {
    size_t n_ptrs = n / sizeof(ptr);
    asm volatile("cld\n\t"
                 "rep ; movsq"
                 : "=D" (dst), "=S" (src)
                 : "c" (n_ptrs), "D" (dst), "S" (src)
                 : "memory");
}