引用《英特尔64和IA-32体系结构优化参考手册》§2.4.6“REP字符串增强”:
使用REP-string的性能特征可归因于两个部分:启动开销和数据传输吞吐量。
[...]
对于更大粒度数据传输的REP字符串,随着ECX值的增加,REP字符串的启动开销会逐步增加:
>
中间字符串长度:REP-MOVSW/MOVSD/MOVSQ的延迟有大约15个周期的启动成本,再加上word/dword/qword中数据移动每次迭代的一个周期。
(重点矿山)
没有进一步提及此类启动成本。这是怎么一回事?它做什么?为什么总是需要更多的时间?
该专利表明,解码器能够确定最后一次移动到rcx是立即的,还是以某种方式进行了修改,使得解码器对rcx中的值未知。它通过在解码立即数mov时将一个位设置为rcx,并将其称为“快速字符串位”,并将立即数值存储在寄存器中。该位在解码以未知方式修改rcx的指令时被清除。如果设置了该位,则该位跳转到单独微码例程中的一个位置,该微码例程的大小可能为12个重复——如果rcx=5,则跳转到重复7,即它保留的寄存器中的立即数值为5。这是一个不包含微分支的快速实现。如果未按照SGX文件中关于较大阵列的“微码辅助”的规定进行设置,则当已知rcx的值时,它可能会发出一个uop,该uop会在失效时陷阱到慢循环微码例程,尽管这更像是一个总是陷阱的“陷阱”uop,而不是可能导致需要“辅助”的uop。或者,正如专利所建议的(“否则,指令转换器206将控制权转移到循环REP MOVS微指令序列”),MSROM可以改为立即内联执行慢速例程,并且它只是继续发出重复和循环,直到分支预测失误,并最终纠正为不执行,微码结束。
我假设常规(循环)MSROM过程主体中的微分支将由uop本身(在操作码中)静态预测,因为这是一个将执行多次且预测失误一次的循环。因此,这种快速方法只能消除序列末尾的分支预测失误以及每次迭代的微分支指令,从而减少UOP的数量。大部分预测失误发生在Peter提到的设置中,这似乎是P6“快速字符串”的设置(显然与专利中的术语“快速字符串”无关,它位于P6之后),或者实际上是ERMSB,我认为这只发生在专利中提到的慢速(循环)例程中。在慢速例行程序中,如果ecx
微分支预测失误代价高昂,因为它必须刷新整个管道,重新蚀刻rep movs指令,然后在预测失误的微ip上恢复解码,在可能已经完成解码且其他UOP正在解码后返回MSROM过程。BOB也可能用于微分支预测失误,这比宏观分支预测失误更有益。RAT快照可能与每条分支指令的ROB条目相关联。
您的报价仅适用于Nehalem微架构(英特尔酷睿i5、i7和Xeon处理器于2009年和2010年发布),英特尔对此很明确。
在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),用于非常大的块-在我们支付了40个周期的启动成本后,只需4个周期就可以复制每个后续64字节的数据(如果数据与缓存线边界对齐的话)-当我们复制256字节或更多字节时,这非常好,而且您不需要使用XMM寄存器!
因此,在Nehalem微体系结构上,REP MOVSB/MOVSW几乎没有用处,但当我们需要复制超过256字节的数据,并且数据与缓存线边界对齐时,REP MOVSD/MOVSQ非常好。
在之前的Intel微架构(2008年之前)上,启动成本甚至更高。自1996年Pentium Pro(P6)以来,Intel x86处理器就有了“快速字符串”。P6快速字符串采用REP MOVSB和更大的,并使用64位微代码加载和存储以及非RFO(读取所有权)缓存协议来实现它们。它们没有违反内存排序,不像Ivy Bridge中的ERMSB。
2019年9月推出的冰湖微体系结构引入了Fast Short REP MOV(FSRM)。此功能可通过CPUID位进行测试。它旨在使128字节及以下的字符串也很快,但事实上,使用rep movsb时,64字节之前的字符串仍然比使用简单的64位寄存器复制时慢。此外,FSRM仅在64位下实现,而不是在32位下实现。至少在我的i7-1065G7 CPU上,rep-movsb只能快速处理64位以下的小字符串,但在32位体系结构上,字符串必须至少为4KB,以便rep-movsb开始优于其他方法。
以下是源和目标位于一级缓存中时的REP MOV测试,测试块的大小足以不受启动成本的严重影响,但不会太大而超过一级缓存的大小。资料来源: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
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
韦斯特米尔(2010-2011)
REP MOVSB 21.14 B/c
REP MOVSW 19.11 B/c
REP MOVSD 24.27 B/c
常春藤大桥(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)-具有增强的代表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
卡比湖(2016-2017年)-具有增强的代表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的实现在不同的微体系结构中有很大的不同。
据Intel称,在Nehalem上,大于9字节的字符串的REP-MOVSB启动成本为50个周期,但对于REP-MOVSW/MOVSD/MOVSQ,其启动成本为35到40个周期,因此REP-MOVSB的启动成本更高;测试表明,REP MOVSW的总体性能最差,而Nehalem和Westmile的REP MOVSB的性能最差。
在常春藤桥、天空湖和卡比湖,这些指令的结果相反:REP MOVSB比REP MOVSW/MOVSD/MOVSQ快,尽管只是稍微快一点。在常春藤桥上,REP MOVSW仍然落后,但在天空湖和卡比湖上,REP MOVSW并不比REP MOVSD/MOVSQ差。
请注意,我提供了SkyLake和Kaby Lake的测试结果,这些结果取自instaltx64站点,只是为了确认-这些体系结构具有相同的每个指令数据周期。
结论:您可以将MOVSD/MOVSQ用于非常大的内存块,因为它可以在从Yohan到Kaby Lake的所有Intel微体系结构上产生足够的结果。尽管在Yonan架构和更早版本上,SSE copy可能会比REP MOVSD产生更好的结果,但出于通用性考虑,REP MOVSD是首选。除此之外,REP-MOVS*可能会在内部使用不同的算法来处理缓存,这对于普通指令来说是不可用的。
关于非常小的字符串(小于9字节或小于4字节)的REP MOVSB-我甚至不会推荐它。在Kaby Lake上,即使没有REP
,单个MOVSB
也是4个周期,在Yohan上是5个周期。根据上下文,您可以仅使用普通MOV做得更好。
正如您所写的,启动成本不会随着大小的增加而增加。增加的是完成整个字节序列的整体指令的延迟——这很明显——您需要复制更多的字节,需要更多的周期,即整体延迟,而不仅仅是启动成本。英特尔没有透露小字符串的启动成本,它只指定了Nehalem的76字节及以上的字符串。例如,以Nehalem的数据为例:
请注意,只有rep movs
和rep stos
是快速的。当前CPU上的repe/ne
cmp
和scas
一次只循环1个元素。(https://agner.org/optimize/有一些perf数字,例如repe cmpsb
的每个RCX计数2个周期)。不过,它们仍然有一些微码启动开销。
rep movs微码有几种策略可供选择。如果src和dest没有紧密重叠,那么微代码循环可以传输更大的64b块。(这是P6引入的所谓“快速字符串”功能,偶尔会为支持更广泛加载/存储的后续CPU重新调整)。但是,如果dest与src只有一个字节,那么rep-movs必须产生与许多单独的mov指令完全相同的结果。
因此,微码必须检查重叠,并且可能检查对齐(src和est分开,或相对对齐)。它可能还根据小/中/大计数器值选择一些东西。
根据Andy Glew对为什么复杂的memcpy/memset更优秀的答案的评论?,微码中的条件分支不受分支预测的影响。因此,如果默认的未执行路径不是实际执行的路径,则在启动周期中会有很大的损失,即使对于使用相同对齐和大小的相同rep mov的循环也是如此。
他监督了P6中的初始rep
字符串实现,所以他应该知道。:)
REP MOVS使用常规代码无法使用的缓存协议功能。基本上与SSE流媒体存储类似,但与正常的内存排序规则等兼容。//选择和设置正确方法的巨大开销主要是由于缺少微码分支预测。我一直希望我使用硬件状态机而不是微码来实现REP-mov,这样可以完全消除开销。
顺便说一句,我早就说过,硬件可以比软件做得更好/更快的事情之一是复杂的多路分支。
自1996年我监管的奔腾Pro(P6)以来,英特尔x86就有了“快速字符串”。P6 fast字符串使用REP MOVSB和更大的版本,并使用64位微码加载和存储以及无RFO缓存协议来实现它们。与iVB中的ERMSB不同,它们没有违反内存顺序。
在微码中执行快速字符串的最大缺点是:(a)微码分支预测失误,(b)微码与每一代人都不协调,速度越来越慢,直到有人来修复它。就像图书馆里的人一样,抄本也会走调。我认为可能错过的机会之一是在128位加载和存储可用时使用它们,等等
现在回想起来,我应该编写一个自调优基础设施,以便在每一代上都获得相当好的微码。但这无助于使用新的、更广泛的装载和存储,当它们可用时Linux内核似乎有这样一个在引导时运行的自动调整基础设施然而,总的来说,我提倡硬件状态机可以在模式之间平稳过渡,而不会导致分支预测失误很好的微码分支预测是否可以避免这种情况,这是有争议的。
基于此,我对具体答案的最佳猜测是:对于中间长度,通过微码的快速路径(尽可能多的分支实际采用默认的未采用路径)是15个周期的启动情况。
由于Intel没有公布全部详细信息,因此我们只能对各种大小和对齐的周期计数进行黑盒测量。幸运的是,这就是我们做出正确选择所需要的一切。英特尔手册,以及http://agner.org/optimize/,了解有关如何使用rep MOV的详细信息。
有趣的事实:如果没有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移动/存储B的CPU上也是如此。尤其是在对齐没有保证的情况下。(用于memcpy的增强REP MOVSB,另请参阅英特尔的优化手册。x86标签wiki中的链接)
进一步的细节:我想在某处有一些讨论,关于测试rep movsb如何影响周围指令的无序执行,以及稍后指令的UOP多久可以进入管道。我想我们在英特尔的一项专利中找到了一些信息,对这一机制有了一些了解。
微码可以使用一种预测的加载和存储uop,它可以在不知道RCX值的情况下发出一组uop。如果RCX值很小,那么一些UOP会选择不做任何事情。
我在Skylake上对
rep movsb
进行了一些测试。它似乎与初始突发机制一致:低于某个大小阈值(如96字节或其他东西),IIRC性能对于任何大小几乎都是恒定的。(L1d缓存中的小对齐缓冲区很热)。我让rep movs
在一个循环中使用独立的imul
依赖链,测试它是否可以重叠执行。
但是后来有一个显着的下降超过了这个大小,大概是当微码音序器发现它需要发出更多的复制uops时。所以我认为当
rep movsb
microcoded-uop到达IDQ的前端时,它会让微码音序器发出足够的加载存储uops以满足某个固定大小,并检查这是否足够或是否需要更多。
这都是记忆,我没有在更新这个答案的时候重新测试。如果这对其他人来说不符合现实,请告诉我,我会再检查一遍。
我在理解promise方面有一个(看似基本的)问题。首先是代码: 每个promise函数在添加到promise数组时被调用,而不是像我所想的那样调用Q.all。 我有什么不明白的? 如何在不立即调用所述promise的情况下将一系列promise排队?
问题内容: 我已经对流进行了一些测试,特别是对nio- package的DirectoryStreams进行了流测试。我只是尝试获取目录中所有文件的列表,该列表按上次修改日期和大小排序。 旧File.listFiles()的JavaDoc对File中 的 方法进行了注释: 请注意,Files类定义了newDirectoryStream方法来打开目录并遍历目录中的文件名。当使用非常大的目录时,这可能
本文向大家介绍什么是用例执行结果?相关面试题,主要包含被问及什么是用例执行结果?时的应答技巧和注意事项,需要的朋友参考一下 通过,不通过,未运行,无法运行
问题内容: 我正在学习Go,并且想尝试goroutine和频道。 这是我的代码: 结果如下: 我不明白为什么我的goroutine永远不会执行。没有输入“进入goroutine”,并且没有任何错误消息。 问题答案: 事实是您的goroutine开始执行,但是在执行任何操作之前就结束了,因为您的程序在打印后立即停止:goroutine的执行与主程序无关,但是将在与程序相同的位置处停止。因此,基本上,
问题内容: 我在MacOSX上使用Docker(带有Boot2Docker)。 我可以从Docker Hub运行映像。 但是,当我尝试像这样运行自己的映像之一时: 要么 要么 我得到: 我猜它找不到在容器中执行的bash二进制文件,但是为什么呢? 基本图像是 谢谢你的帮助。 阿什莉 问题答案: 您的图片基于不带bash外壳的busybox。它的确有外壳。 所以这不起作用: 但这确实是: 由于您的入
有人能告诉我“创建付款”和“执行付款”之间的区别吗?谢谢 创建付款示例:http://paypal.github.io/PayPal-PHP-SDK/sample/doc/payments/ExecutePayment.html "执行付款"示例:http://paypal.github.io/PayPal-PHP-SDK/sample/doc/payments/ExecutePayment.ht