edm*_*dmz 13 optimization performance x86 assembly
引用英特尔 ®64 和IA-32架构优化参考手册,§2.4.6"REP String Enhancement":
使用REP字符串的性能特征可归因于两个组件: 启动开销和数据传输吞吐量.
[...]
对于较大粒度数据传输的REP字符串,随着ECX值的增加,REP String的启动开销呈逐步增加:
- 短串(ECX <= 12):REP MOVSW/MOVSD/MOVSQ的延迟约为20个周期,
快速字符串(ECX> = 76:不包括REP MOVSB):处理器实现通过移动尽可能多的16字节数据来提供硬件优化.如果其中一个16字节数据传输跨越缓存行边界,则REP字符串延迟的延迟会有所不同:
- 无拆分:延迟包括大约40个周期的启动成本,每个64字节的数据增加4个周期,
- 高速缓存拆分:延迟包括大约35个周期的启动成本,每64个字节的数据增加6个周期.
中间字符串长度:REP MOVSW/MOVSD/MOVSQ的延迟具有大约15个周期的启动成本加上word/dword/qword中数据移动的每次迭代的一个周期.
(强调我的)
没有进一步提及这种启动成本.它是什么?它做了什么,为什么总是需要更多的时间?
Pet*_*des 18
该rep movs
微码有几个战略选择. 如果 src和dest不紧密重叠,则微编码循环可以以更大的64b块传输.(这是P6引入的所谓"快速字符串"功能,偶尔会针对以后支持更宽负载/存储的CPU进行重新调整).但是如果dest只是src中的一个字节,rep movs
则必须产生与许多单独movs
指令相同的结果.
因此微码必须检查重叠,并且可能用于对齐(分别为src和dest,或相对对齐).它可能还会根据小/中/大计数器值选择一些东西.
根据Andy Glew的评论回答为什么复杂的memcpy/memset优越?,微码中的条件分支不受分支预测的影响.因此,如果默认的非采用路径不是实际采用的路径,则启动周期会有明显的损失,即使对于使用相同路径rep movs
和大小的路径也是如此.
他监督了rep
P6中的初始字符串实现,所以他应该知道.:)
REP MOVS使用常规代码无法使用的缓存协议功能.基本上类似于SSE流存储,但是以与正常存储器排序规则等相容的方式.//"选择和设置正确方法的大开销"主要是由于缺少微码分支预测.我一直希望我使用硬件状态机而不是微代码来实现REP MOVS,这可以完全消除开销.
顺便说一下,我早就说过,硬件能比软件做得更好/更快的事情之一就是复杂的多路分支.
自1996年Pentium Pro(P6)以来,英特尔x86拥有"快速字符串",这是我监督的.P6快速字符串采用REP MOVSB和更大版本,并使用64位微码加载和存储以及无RFO缓存协议实现它们.与iVB中的ERMSB不同,它们没有违反内存排序.
在微码中做快速字符串的一大弱点是(a)微码分支错误预测,以及(b)微代码与每一代都失去了调整,变得越来越慢,直到有人来解决它.就像图书馆一样,男人的副本也会失控.我想错过的机会之一是有可能在它们可用时使用128位加载和存储,依此类推
回想起来,我应该编写一个自我调整的基础设施,以便在每一代人身上获得相当好的微码.但是,当它们可用时,这将无助于使用新的,更宽的装载和存储.// Linux内核似乎有这样的自动调整基础架构,它在启动时运行.//但总的来说,我提倡可以在模式之间平滑过渡的硬件状态机,而不会导致分支错误预测.//良好的微码分支预测是否可以避免这一点,这是有争议的.
基于此,我对特定答案的最佳猜测是:通过微码的快速路径(尽可能多的分支实际上采用默认的非采用路径)是15周期启动情况,对于中间长度.
由于英特尔不公布完整的详细信息,因此我们可以做到最好的各种尺寸和路线的循环计数黑盒测量. 幸运的是,我们需要做出正确的选择. 英特尔手册和http://agner.org/optimize/提供了有关如何使用的详细信息rep movs
.
有趣的事实:没有ERMSB(IvB和更高版本):rep movsb
针对小型副本进行了优化.启动比需要更长时间(rep movsd
或者rep movsq
我认为超过几百个字节)复制,甚至在此之后可能无法实现相同的吞吐量.
没有ERMSB和没有SSE/AVX的大型对齐副本的最佳序列(例如在内核代码中)可能会被rep movsq
清理,例如未对齐mov
,复制缓冲区的最后8个字节,可能与最后一个对齐的块重叠做了什么rep movsq
.(基本上使用glibc的小副本memcpy
策略).但是如果大小可能小于8个字节,则需要分支,除非可以安全地复制比所需更多的字节.或者rep movsb
,如果小代码大小比性能更重要,则可以选择清理.(rep
如果RCX = 0,将复制0个字节).
SIMD矢量循环通常至少比rep movsb
具有增强型Rep Move/Stos B的CPU 快一些.特别是如果不能保证对齐.(针对memcpy的增强型REP MOVSB,另请参阅英特尔的优化手册.x86 标签wiki中的链接)
您所提供的报价仅适用于Nehalem微体系结构(2009年和2010年发布的英特尔酷睿i5,i7和Xeon处理器),而英特尔则明确表示.
在Nehalem之前,REP MOVSB甚至更慢.英特尔对后来的微体系结构发生的事情保持沉默,但是,随着Ivy Bridge微架构(2012年和2013年发布的处理器),英特尔推出了增强型REP MOVSB(我们仍然需要检查相应的CPUID位),这使得我们可以复制记忆力很快.
最便宜的后续处理器版本 - 2017年发布的Kaby Lake"Celeron"和"Pentium",没有可用于快速内存复制的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年之前)中,启动成本甚至更高.
以下是当源和目标位于L1缓存中时,REP MOVS*的测试,其大小足以不受启动成本的严重影响,但不会超过L1缓存大小.资料来源:http://users.atw.hu/instlatx64/
约拿(2006-2008)
REP MOVSB 10.91 B/c
REP MOVSW 10.85 B/c
REP MOVSD 11.05 B/c
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
Westmere(2010-2011)
REP MOVSB 21.14 B/c
REP MOVSW 19.11 B/c
REP MOVSD 24.27 B/c
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
如您所见,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差.
请注意,我已经提供了来自instaltx64站点的 SkyLake和Kaby Lake的测试结果,仅仅是为了确认 - 这些架构具有相同的每指令周期数据.
结论:您可以将MOVSD/MOVSQ用于非常大的内存块,因为它可以在从Yohan到Kaby Lake的所有英特尔微体系结构上产生足够的结果.虽然,在Yonan架构和之前,SSE副本可能比REP MOVSD产生更好的结果,但是,为了普遍性,REP MOVSD是首选.除此之外,REP MOVS*可以在内部使用不同的算法来处理缓存,这对于正常指令是不可用的.
至于REP MOVSB对于非常小的字符串(少于9个字节或少于4个字节) - 我甚至不推荐它.在Kaby Lake上,单个MOVSB
甚至没有REP
4个周期,在Yohan上它是5个周期.根据具体情况,您可以使用普通MOV做得更好.
正如您所写的那样,启动成本不会随着尺寸的增加而增加.整个指令的延迟是完成整个字节序列的增加 - 这是非常明显的 - 需要复制更多字节,需要更多周期,即整体延迟,而不仅仅是启动成本.英特尔没有透露小字符串的启动成本,它只为Nehalem指定了76字节以上的字符串.例如,获取有关Nehalem的数据: