REP做什么设置?

时间:2015-11-24 19:21:27

标签: performance assembly optimization x86

引用英特尔®64和IA-32架构优化参考手册,§2.4.6“REP String Enhancement”:

  

使用REP字符串的性能特征可以归结为两个组件:    启动开销 和数据传输吞吐量。

     

[...]

     

对于较大粒度数据传输的REP字符串,作为ECX值    增加, REP String的启动开销逐步增加

     
      
  • 短字符串(ECX <= 12):REP MOVSW / MOVSD / MOVSQ的延迟大约 20个周期
  •   
  • 快速字符串(ECX&gt; = 76:   不包括REP MOVSB):处理器实现提供硬件   通过尽可能多地移动16个字节的数据进行优化。   如果其中一个16字节数据,REP字符串延迟的延迟会有所不同   传输跨越缓存行边界:

         
        
    • 无拆分:延迟包括启动成本约40个周期,每64个字节的数据增加4个周期,
    •   
    • 缓存拆分:延迟包含启动   大约35个周期的成本,每64字节的数据增加6个周期。
    •   
  •   
  • 中间字符串长度:REP MOVSW / MOVSD / MOVSQ的延迟   启动成本约为15个周期加上每个迭代的一个周期   word / dword / qword中的数据移动。

  •   

(强调我的)

没有进一步提及此类启动成本。它是什么?它做了什么,为什么总是需要更多时间?

4 个答案:

答案 0 :(得分:17)

rep movs微码有几种策略可供选择。 如果 src和dest没有紧密重叠,则微编码循环可以以更大的64b块传输。 (这是P6引入的所谓“快速字符串”功能,偶尔会针对以后支持更宽负载/存储的CPU进行重新调整)。但是如果dest只是src中的一个字节,那么rep movs必须产生与许多单独的movs指令相同的结果。

因此微码必须检查重叠,并且可能是为了对齐(分别是src和dest,或相对对齐)。它可能还会根据小/中/大计数器值选择一些东西。

根据Andy Glew's commentsWhy are complicated memcpy/memset superior?的回答,微码中的条件分支不受分支预测的约束。因此,如果默认的未采用路径不是实际采用的路径,那么启动周期会有很大的损失,即使对于使用相同路径和大小的相同rep movs的循环也是如此。

他监督了P6中的初始rep字符串实现,所以他应该知道。 :)

  

REP MOVS使用不可用的缓存协议功能   常规代码。基本上像SSE流媒体商店,但在某种程度上   这与正常的内存排序规则等兼容。//   “选择和设置正确方法的巨大开销”是   主要是由于缺少微码分支预测。我很久了   希望我使用硬件状态机实现了REP MOVS   而不是微代码,它可以完全消除   开销。

     

顺便说一句,我早就说过硬件可以做的事情之一   比软件更好/更快的是复杂的多路分支。

     

自1996年Pentium Pro(P6)以来,Intel x86拥有“快速字符串”,   我监督过。 P6快速字符串采用REP MOVSB和更大,和   使用64位微码加载和存储以及无RFO实现它们   缓存协议。与ERMSB不同,它们没有违反记忆排序   IVB。

     

在微码中执行快速字符串的一大弱点是(a)微码   分支错误预测,(b)微码与...失调   每一代人都会越来越慢,直到有人出现   修理它。就像图书馆一样,男人的副本也会失控。一世   假设错过的机会之一是可能的   在可用时使用128位加载和存储,等等

     

回想起来,我应该写一个自我调整的基础设施   每一代都能获得相当不错的微码。但那不会   当他们成为时,帮助使用新的,更宽的,装载和商店   可用。 // Linux内核似乎有这样的自动调整   基础设施,在启动时运行。 //但总的来说,我提倡   可以在模式之间平滑过渡的硬件状态机,   不会导致分支错误预测。 //是否有争议   好的微码分支预测可以避免这种情况。

基于此,我对特定答案的最佳猜测是:通过微码的快速路径(尽可能多的分支实际上采用默认的非采用路径)是15周期启动情况,对于中间长度。

由于英特尔不公布完整的详细信息,因此我们可以做到最好的各种尺寸和路线的循环计数黑盒测量。 幸运的是,我们需要做出正确的选择。英特尔手册和http://agner.org/optimize/提供了有关如何使用rep movs的详细信息。

有趣的事实:没有ERMSB(IvB及更高版本):rep movsb针对小型副本进行了优化。启动时间比rep movsdrep movsq需要更长的时间(对于大型(我认为超过几百个字节)),甚至在此之后可能无法实现相同的吞吐量。

没有ERMSB且没有SSE / AVX(例如在内核代码中)的大对齐副本的最佳序列可能是rep movsq,然后使用复制最后8个的未对齐mov进行清理缓冲区的字节,可能与rep movsq所做的最后一个对齐的块重叠。 (基本上使用glibc's small-copy memcpy strategy)。但是如果大小可能小于8个字节,则需要分支,除非可以安全地复制比所需更多的字节。或者rep movsb是一个清理选项,如果小代码大小比性能更重要。 (如果RCX = 0,则rep将复制0个字节。

即使在具有增强型Rep Move / Stos B的CPU上,SIMD向量循环通常至少略快于rep movsb。特别是如果无法保证对齐。 (Enhanced REP MOVSB for memcpy,另请参阅英特尔优化手册。链接in the x86 tag wiki

答案 1 :(得分:7)

您所提供的报价仅适用于Nehalem微体系结构(2009年和2010年发布的英特尔酷睿i5,i7和Xeon处理器),英特尔明确表示。

在Nehalem之前,REP MOVSB甚至更慢。英特尔对后来的微体系结构发生的事情保持沉默,但是,随着Ivy Bridge微架构(2012年和2013年发布的处理器),英特尔推出了增强型REP MOVSB(我们仍然需要检查相应的CPUID位),这使得我们可以复制记忆力很快。

最便宜的后续处理器版本 - Kaby Lake&#34; Celeron&#34;和2017年发布的Pentium&#34;不具备可用于快速内存复制的AVX,但它们仍具有增强型REP MOVSB。这就是为什么REP MOVSB对2013年以来发布的处理器非常有利的原因。

令人惊讶的是,Nehalem处理器对于非常大的块具有相当快的REP MOVSD / MOVSQ实现(但不是REP MOVSW / MOVSB) - 仅复制4个周期来复制每个后续的64字节数据(如果数据与高速缓存线对齐)在我们支付40个周期的启动成本之后 - 当我们复制256个字节以及更多时,这是非常好的,并且您不需要使用XMM寄存器!

因此,在Nehalem微体系结构上,REP MOVSB / MOVSW几乎没用,但是当我们需要复制超过256个字节的数据并且数据与缓存行边界对齐时,REP MOVSD / MOVSQ非常出色。

在以前的英特尔微体系结构(2008年之前)中,启动成本甚至更高。

以下是REP MOVS *的测试,当源和目标位于L1缓存中时,块的大小足以不受启动成本的严重影响,但不会超过L1缓存大小。资料来源:http://users.atw.hu/instlatx64/

Yonah(2006-2008)

    REP MOVSB 10.91 B/c
    REP MOVSW 10.85 B/c
    REP MOVSD 11.05 B/c

Nehalem(2009-2010)

    REP MOVSB 25.32 B/c
    REP MOVSW 19.72 B/c
    REP MOVSD 27.56 B/c
    REP MOVSQ 27.54 B/c

Westmere(2010-2011)

    REP MOVSB 21.14 B/c
    REP MOVSW 19.11 B/c
    REP MOVSD 24.27 B/c

Ivy Bridge(2012-2013) - 增强型REP MOVSB

    REP MOVSB 28.72 B/c
    REP MOVSW 19.40 B/c
    REP MOVSD 27.96 B/c
    REP MOVSQ 27.89 B/c

SkyLake(2015-2016) - 增强型REP MOVSB

    REP MOVSB 57.59 B/c
    REP MOVSW 58.20 B/c
    REP MOVSD 58.10 B/c
    REP MOVSQ 57.59 B/c

Kaby Lake(2016-2017) - 增强型REP MOVSB

    REP MOVSB 58.00 B/c
    REP MOVSW 57.69 B/c
    REP MOVSD 58.00 B/c
    REP MOVSQ 57.89 B/c

如您所见,REP MOVS的实现与一个微体系结构有很大不同。

据英特尔称,在Nehalem上,REP MOVSB大于9字节的字符串启动成本为50个周期,但对于REP MOVSW / MOVSD / MOVSQ,它们的周期为35到40周期 - 因此REP MOVSB的启动成本较高;测试表明,REP MOVSW的整体表现最差,而Nehalem和Westmere则没有REP MOVSB。

在Ivy Bridge,SkyLake和Kaby Lake上,结果与这些指令相反:REP MOVSB比REP MOVSW / MOVSD / MOVSQ快,尽管只是略微。在Ivy Bridge上,REP MOVSW仍然落后,但在SkyLake和Kaby Lake上,REP MOVSW并不比REP MOVSD / MOVSQ差。

请注意,我提供了SkyLake和Kaby Lake的测试结果,仅为了确认而从instaltx64 site获取 - 这些体系结构具有相同的每指令周期数据。

结论:您可以将MOVSD / MOVSQ用于非常大的内存块,因为它可以在从Yohan到Kaby Lake的所有英特尔微架构上产生足够的结果。虽然,在Yonan架构和之前,SSE副本可能比REP MOVSD产生更好的结果,但是,为了普遍性,REP MOVSD是首选。除此之外,REP MOVS *可以在内部使用不同的算法来处理缓存,这对于正常指令是不可用的。

对于非常小的字符串(少于9个字节或少于4个字节)的REP MOVSB - 我甚至都不推荐它。在Kaby Lake上,单个MOVSB甚至没有REP是4个周期,在Yohan上是5个周期。根据具体情况,您可以使用普通MOV做得更好。

正如您所写的那样,启动成本不会随着尺寸的增加而增加。整个指令的延迟是完成增加的整个字节序列 - 这是非常不可思议的 - 需要复制更多的字节,需要更多的周期,即整体延迟,而不仅仅是启动成本。英特尔没有透露小字符串的启动成本,它只为Nehalem指定了76字节以上的字符串。例如,获取有关Nehalem的数据:

  • 如果ECX <10,则MOVSB的等待时间为9个周期。因此,这意味着只要该字符串有1个字节或2个字节或3个字节,复制任何字符串就需要9个周期。这并不是那么糟糕 - 例如,如果您需要复制尾部并且您不想使用orverlapping存储。只需9个周期来确定大小(1到3之间)并实际复制数据 - 使用正常指令和所有这些分支很难实现这一点 - 对于3字节副本,如果你没有复制以前的数据,你将不得不使用2个加载和2个存储(字+字节),并且因为我们最多只有一个存储单元,所以我们不会用正常的MOV指令那么快。
  • 如果ECX介于4到9之间
  • ,英特尔对REP MOVSB的延迟没有提及
  • 短字符串(ECX <= 12):REP MOVSW / MOVSD / MOVSQ的延迟大约是20个周期来复制整个字符串 - 而不仅仅是20个周期的启动成本。因此,复制整个字符串&lt; = 12字节需要大约20个周期,因此我们的每个字节的输出率高于使用ECX&lt;的REP MOVSB的输出率。 4。
  • 使用REP MOVSD / MOVSQ
  • ECX&gt; = 76 - 是的,这里我们确实有40个周期的启动成本,但这是合理的,因为我们以后只使用4个周期复制每个64字节的数据。我不是英特尔工程师,有权回复为什么有启动成本,但我认为这是因为对于这些字符串,REP MOVS *使用(根据Andy Glew的评论,为什么是复杂的答案) memcpy / memset优于来自Peter Cordes的答案)缓存协议功能,常规代码无法使用。在这句话中有一个解释:“选择和设置正确方法的巨大开销主要是由于缺少微码分支预测”。还有一个有趣的说明,Pentium Pro(P6)在1996年使用64位微码加载和存储以及无RFO缓存协议实现了REP MOVS * - 它们不违反内存排序,与Ivy Bridge中的ERMSB不同。

答案 2 :(得分:3)

patent 表明解码器能够确定对 rcx 的最后一次移动是立即移动还是以某种方式对其进行了修改,使得 rcx 中的值在解码器。它通过在将立即 mov 解码为 rcx 并将其称为“快速字符串位”并将立即值存储在寄存器中来实现此目的。该位在解码以未知方式修改 rcx 的指令时被清除。如果该位被设置,那么它会跳转到一个单独的微代码例程中的一个位置,它可能是 12 次重复的大小——如果 rcx = 5 即它保存的寄存器中的立即数是 5,它会跳转到重复 7。这是一个不包含微分支的快速实现。如果它没有被设置,根据 SGX 论文中关于更大阵列的“微码辅助”,那么当 rcx是已知的,尽管这更像是一个总是陷阱的“陷阱”uop,而不是一个可能导致需要“协助”的uop。或者,如专利所建议的('否则,指令翻译器 206 将控制转移到循环 REP MOVS 微指令序列'),MSROM 可以改为立即内联执行慢速例程,它只是继续发出重复并循环直到分支预测错误并最终更正为未采用且微码结束。

我会假设常规(循环)MSROM 过程主体中的微分支将由 uop 本身(在操作码中)静态预测,因为这是一个将执行多次的循环,并且误会一次。因此,这种快速方法只会消除序列末尾的分支错误预测以及每次迭代的微分支指令,从而减少了微指令的数量。主要的错误预测发生在 Peter 提到的设置中,这似乎是 P6“快速字符串”的设置(显然与专利中的术语“快速字符串”无关,它出现在 P6 之后),或者实际上是 ERMSB,我think 只发生在专利提到的慢(循环)例程中。在慢速例程中,如果 ecx >= 76,那么它可以被增强并经历一个初始设置过程,但看起来 ecx 需要超过一定的大小,以便它实际上更快,并具有“快速字符串”或 ERMSB 的启动过程。这将需要知道 ecx 的值,这可能只是可能会错误预测的常规 ecx 比较和跳转。显然,这个缓慢的例程增强也使用了不同的缓存协议,as discussed

微分支错误预测代价高昂,因为它必须flush the whole pipeline, refetch the rep movs instruction and then resume decoding at the mispredicted micro-ip,在它可能已经完成解码并且其他微指令在其后面被解码后返回到MSROM过程。 BOB 也可能与微分支错误预测一起使用,在这种情况下,它比宏分支错误预测更有益。 RAT 快照可能与每个分支指令的 ROB 条目相关联。

答案 3 :(得分:1)

从描述中我发现有一个16字节的最佳传输大小,所以如果要传输的是79字节,那就是4 * 16 + 15.所以不太了解对齐,这可能意味着有一个前面或末尾(或分割)15个字节的成本和4个16字节的传输比16的分数快。有点像你的汽车中的高速档与通过档位向高速档升档。

查看glibc或gcc或其他地方的优化memcpy。它们可以传输几个单独的字节,然后它们可以进行16位传输,直到它们达到32位对齐,64位对齐,128位对齐地址的最佳对齐大小,然后它们可以进行多字传输。大部分副本,然后他们降档,也许一个32位的东西可能是一个16可能1字节,以弥补后端缺乏对齐。

听起来像rep做同样的事情,单次转移不足以达到优化的对齐大小,然后大转移直到接近结束然后可能是一些小的单独转移以覆盖最后一部分。