用于memcpy的增强型REP MOVSB


71

我想使用增强的REP MOVSB(ERMSB)为自定义获取高带宽memcpy

ERMSB是与Ivy Bridge微体系结构一起引入的。如果您不知道什么是ERMSB,请参阅英特尔优化手册中的“增强型REP MOVSB和STOSB操作(ERMSB)”部分。

我知道直接执行此操作的唯一方法是内联汇编。我从https://groups.google.com/forum/#!topic/gnu.gcc.help/-Bmlm_EG_fE获得了以下功能

但是,当我使用它时,带宽远小于memcpy。 我的i7-6700HQ(Skylake)系统,Ubuntu 16.10,DDR4 @ 2400 MHz双通道32 GB,GCC 6.2可达到__movsb15 GB / s和memcpy26 GB / s。

为什么带宽这么低REP MOVSB?我该怎么做才能改善它?

这是我用来测试的代码。


我感兴趣的原因rep movsb基于这些评论

注意,在Ivybridge和Haswell上,如果缓冲区较大以适合MLC,则可以使用rep movsb击败movntdqa。movntdqa导致LLC产生RFO,rep movsb不会...当在Ivybridge和Haswell上流传输到内存时,rep movsb的速度明显高于movntdqa(但请注意,在Ivybridge之前它很慢!)

此memcpy实施中缺少什么/欠佳?


这是我在tinymembnech的同一系统上得到的结果

请注意,在我的系统SSE2 copy prefetched上,速度也比快MOVSB copy


在我的原始测试中,我没有禁用turbo。我禁用了turbo并再次进行了测试,但似乎没有太大的区别。但是,更改电源管理确实有很大的不同。

当我做

有时我会看到超过20 GB / s的速度rep movsb

我所看到的最好的速度约为17 GB / s。但memcpy似乎对电源管理并不敏感。


我检查的频率(使用turbostat具有和不具有的SpeedStep启用,以performancepowersave空闲,1-芯负载和一个4芯的负载。我运行Intel的MKL密集矩阵乘法来创建负载并使用设置线程数OMP_SET_NUM_THREADS。这是结果表(GHz中的数字)。

这表明powersave即使在禁用SpeedStep的情况下,CPU仍将时钟降至空闲频率0.8 GHz。只有在performance没有SpeedStep的情况下,CPU才能以恒定频率运行。

我使用了例如sudo cpufreq-set -r performance(因为cpufreq-set给出了奇怪的结果)来更改电源设置。这会重新打开turbo,因此之后我必须禁用turbo。


7
@KerrekSB您知道增强型代表movsb是什么吗?
fuz

3
@ Ped7g,我认为它不会比更好memcpy。我希望它和大约一样好memcpy。我使用gdb进行了遍历,memcpy然后看到它使用进入了一个主循环rep movsb。因此,memcpy无论如何,这似乎是使用的东西(在某些情况下)。
Z玻色子

3
@KerrekSB,是的,它位于“ 3.7.6增强型REP MOVSB和STOSB操作(ERMSB)
Z玻色子”中,

3
优化手册表明,ERMSB比传统的REP-MOV / STO在提供较小的代码大小和吞吐量方面更好,但是“使用ERMSB实施memcpy可能无法达到与使用256位或128位AVX替代方案相同的吞吐量水平,取决于长度和对齐方式。” 我的理解是,此功能在以前可能已经使用过rep指令的情况下得到了增强,但它并不旨在与现代高通量替代品竞争。
Kerrek SB

5
@Zboson我的glibcmemcpy()使用AVX NT商店。NT存储区和ERMSB都以写合并的方式运行,因此不需要RFO。但是,我在自己的计算机上进行的基准测试显示,我memcpy()和我的ERMSB都限制在总带宽2/3倍,就像您memcpy()(但不是您的ERMSB)那样。因此,显然在某处存在额外的总线事务,并且这很麻烦就像RFO。
Iwillnotexist Idonotexist

Answers:


97

这是一个与我的内心和最近的研究息息相关的话题,因此,我将从几个角度进行研究:历史,一些技术说明(主要是学术性的),盒子上的测试结果,最后尝试回答您的实际问题何时何地rep movsb可能有意义。

某种程度上,这是一个共享结果电话-如果您可以运行Tinymembench并共享结果以及CPU和RAM配置的详细信息,那就太好了。特别是如果您有4通道设置,Ivy Bridge盒,服务器盒等。

历史和官方建议

快速字符串复制指令的性能历史一直是一段阶梯式的事情-即停滞的性能时期与大型升级交替出现,使它们投入生产甚至比竞争对手的方法更快。例如,Nehalem(主要针对启动开销)和Ivy Bridge(主要针对大副本的总吞吐量)的性能都有所提高。您可以在此线程中发现十年来对实施rep movs英特尔工程师指令的困难的见解。

例如,在引入常春藤桥之前的指南中,典型的建议是避免使用它们或非常小心地使用它们1

当前的指南(2016年6月,指南)包含各种令人困惑且有些不一致的建议,例如2

在执行时根据数据布局,对齐方式和计数器(ECX)值选择实现的特定变体。例如,具有REP前缀的MOVSB / STOSB应该与小于或等于3的计数器值一起使用以获得最佳性能。

那么对于3个字节或更少字节的副本?首先,您不需要rep前缀,因为声称的启动延迟为〜9个周期,因此您几乎可以肯定,使用简单的DWORD或QWORDmov并通过一点点纠缠来掩盖未使用的字节(或可能带有2个显式字节,mov如果您知道大小正好为3,则为s。

他们接着说:

字符串MOVE / STORE指令具有多种数据粒度。为了有效的数据移动,较大的数据粒度是可取的。这意味着可以通过将任意计数器值分解为多个双字加上计数值小于或等于3的单字节移动来获得更高的效率。

在使用ERMSB的当前硬件上,这肯定rep movsb比大副本的movdor或movq变体至少快或快。

通常,本指南的第(3.7.5)节包含合理且过时的建议的组合。这是英特尔手册的常见吞吐量,因为它们会针对每种体系结构进行增量更新(并且即使在当前手册中也要涵盖将近二十年的体系结构),并且通常不会更新旧部分以替换或提供有条件的建议不适用于当前架构。

然后,他们继续在第3.7.6节中明确介绍ERMSB。

我不会详尽地讨论剩余的建议,但是我将在下面的“为什么使用它”中总结其主要内容。

该指南的其他重要声明是,在Haswell上,rep movsb已进行了增强,可以在内部使用256位操作。

技术注意事项

这只是rep实现的角度对指令具有的基本优点和缺点的简要概述。

优势 rep movs

  1. 当发出repmovs指令时,CPU知道将传输整个已知大小的块。这可以帮助它以离散指令无法实现的方式优化操作,例如:

    • 当知道整个缓存行将被覆盖时,避免使用RFO请求。
    • 立即准确地发出预取​​请求。硬件预取在检测类似memcpy模式方面做得很好,但仍需要进行几次读取才能启动,并且将“超预取”复制区域末尾的许多缓存行。rep movsb确切知道区域大小并且可以准确地预取。
  2. 显然,与必须服从相当严格的内存排序4的简单指令相比,并不能保证在3个内存之内对单个命令进行排序rep movs可以帮助简化一致性流量和块移动的其他方面。mov

  3. 原则上,该rep movs指令可以利用ISA中未公开的各种体系结构技巧。例如,体系结构可能具有ISA公开5的更宽的内部数据路径,并且rep movs可以在内部使用它。

缺点

  1. rep movsb必须实现可能比基础软件要求强的特定语义。特别是,memcpy禁止重叠区域,因此可以忽略这种可能性,但rep movsb允许它们并且必须产生预期的结果。在当前的实现中,大多数情况下会影响启动开销,但可能不会影响大块吞吐量。同样,rep movsb即使您实际上是使用字节粒度的副本来复制大块(该大小是2的幂的整数倍),也必须支持字节粒度的副本。

  2. 如果使用,该软件可能包含有关对齐,副本大小和可能出现的别名的信息,这些信息无法传达给硬件rep movsb。编译器通常可以确定内存块6的对齐方式,因此可以避免每次调用rep movs必须执行的许多启动工作。

试验结果

以下是tinymembench2.6 GHz i7-6700HQ上许多不同复制方法的测试结果(太糟糕了,我具有相同的CPU,所以我们没有获得新的数据点...):

一些关键要点:

  • rep movs方法比不是“非时间” 7的所有其他方法要快,并且比一次复制8个字节的“ C”方法要快得多。
  • “非临时”方法要快一些,比方法快26%rep movs-但这要比您报告的方法小得多(26 GB / s对15 GB / s =〜73%)。
  • 如果您不使用非临时存储,则使用C中的8字节副本几乎与128位宽的SSE加载/存储一样好。那是因为一个好的复制循环可以产生足够的内存压力来饱和带宽(例如,2.6 GHz * 1个存储/周期* 8字节= 26 GB / s用于存储)。
  • tinymembench中没有显式的256位算法(可能是“ standard”除外memcpy),但是由于上述注意,它可能无关紧要。
  • 非临时存储方法的吞吐量比临时方法增加的吞吐量约为1.45倍,这非常接近如果NT消除了3个传输中的1个(即1读,1个NT写与2个写)时的1.5倍。读1写)。这些rep movs方法位于中间。
  • 较低的内存延迟和适度的2通道带宽相结合,意味着该特定芯片恰巧能够从单线程中饱和其内存带宽,从而极大地改变了行为。
  • rep movsd似乎使用了与rep movsb该芯片相同的魔术。这很有趣,因为ERMSB仅明确地针对目标,movsb而使用ERMSB的较早拱门上的较早测试显示,其movsb执行速度比快得多movsd。这主要是学术性的,因为movsb它比以前更笼统movsd

哈斯韦尔

查看iwillnotexist在评论中提供的Haswell结果,我们看到了相同的总体趋势(提取了最相关的结果):

rep movsb方法仍然比非临时方法慢memcpy,但此处仅降低了约14%(相比之下,Skylake测试中为约26%)。现在,NT技术在其临时表亲之上的优势约为57%,甚至比带宽减少的理论优势还要多。

什么时候使用rep movs

最后,刺痛您的实际问题:何时或为何使用它?它基于以上内容,并介绍了一些新的想法。不幸的是,没有简单的答案:您将不得不权衡各种因素,包括一些您甚至可能无法确切知道的因素,例如未来的发展。

请注意,替代方法rep movsb可能是优化的libc memcpy(包括编译器内联的副本),也可能是手动memcpy版本。下面的某些优点仅与这些替代方法中的一个或另一个相比适用(例如,“简单性”对手动滚动版本memcpy有所帮助,而对内置版本则无济于事),而某些优点则两者均适用。

可用说明的限制

在某些环境中,对某些指令或使用某些寄存器有限制。例如,在Linux内核中,通常不允许使用SSE / AVX或FP寄存器。因此,大多数优化的memcpy变体都不能使用,因为它们依赖于SSE或AVX寄存器,并且mov在x86上使用了基于纯64位的副本。对于这些平台,使用rep movsb可以优化大多数性能,memcpy而不会破坏对SIMD代码的限制。

一个更通用的示例可能是必须针对许多代硬件的代码,并且不使用特定于硬件的调度(例如,使用cpuid)。在这里,您可能被迫仅使用较旧的指令集,这排除了所有AVX等,rep movsb在这里可能是一个好方法,因为它允许“隐藏”访问更广泛的负载和存储,而无需使用新的指令。如果您以ERMSB之前的硬件为目标,则必须查看rep movsb那里的性能是否可以接受...

未来打样

从理论上讲rep movsb,它的一个不错的方面是它可以利用对未来体系结构的体系结构改进而无需更改源代码,而显式移动是无法实现的。例如,当引入256位数据路径时,就可以利用它们(如Intel所述),而无需对软件进行任何更改。使用128位移动的软件(在Haswell之前是最佳选择)必须进行修改和重新编译。rep movsb

因此,这既是软件维护的好处(无需更改源)又是现有二进制文件的好处(无需部署新的二进制文件以利用改进的好处)。

这有多重要取决于您的维护模型(例如,实践中多长时间部署一次新的二进制文件),并且很难判断这些指令将来的运行速度。不过,至少英特尔会通过在未来至少保证合理的性能(15.3.3.6)来指导这一方向的使用:

REP MOVSB和REP STOSB在未来的处理器上将继续表现良好。

与后续工作重叠

memcpy当然,这种好处不会在简单的基准中显示出来,根据定义,该基准不会有后续工作重叠,因此,在实际情况下,必须仔细衡量收益的大小。要充分利用优势,可能需要对周围的代码进行重新组织memcpy

英特尔在其优化手册(第11.16.3.4节)中指出了这一优势,他们的话是:

当已知计数至少为一千个字节或更多时,使用增强型REP MOVSB / STOSB可以提供另一项优势,以摊销非使用代码的成本。可以使用Cnt = 4096的值和memset()作为示例来理解启发式:

•memset()的256位SIMD实现将需要使用VMOVDQA发出/执行128个32字节存储操作的退休实例,然后才能使非消耗指令序列退役。

•ECX = 4096的增强型REP STOSB实例被解码为硬件提供的长微操作流,但作为一条指令退出。必须先完成许多store_data操作,才能使用memset()的结果。由于存储数据操作的完成与程序顺序的退出是分离的,因此非消耗代码流的很大一部分都可以通过发布/执行和退出来进行处理,如果非消耗序列不竞争,则基本上是免费的用于存储缓冲区资源。

因此,英特尔表示,毕竟rep movsb已经发布了一些代码,但是尽管许多商店还在运营中,并且rep movsb总体上还没有退休,但是遵循说明的uops可以在乱序中取得更大的进步机械,如果代码在复制循环之后出现,他们将无法做到。

来自显式加载和存储循环的微指令实际上必须按照程序顺序分别退出。一定要在ROB中留出空间来进行后续操作。

关于微码指令的rep movsb工作时间,确切地来说似乎没有太多详细的信息。我们不确切知道微代码分支如何从微代码定序器请求不同的uops流,或者uops如何退休。如果各个微指令不必单独退休,那么整个指令可能只占用ROB中的一个插槽?

当送入OoO机械的前端在uoprep movsb缓存中看到一条指令时,它将激活微码定序器ROM(MS-ROM),以将微码uops发送到送入问题/重命名阶段的队列中。其他uops可能无法与之混合并在仍在发出时执行/执行8rep movsb,但是可以在最后一次rep movsbuop之后立即获取/解码并发出后续指令,而某些副本尚未执行。仅在至少某些后续代码不依赖的结果时才有用memcpy(这并不罕见)。

现在,此好处的大小是有限的:除了慢rep movsb指令之外,您最多可以执行N条指令(实际上是微指令),此时您将停顿,其中N是ROB大小。使用当前的ROB大小约为200(在Haswell上为192,在Skylake上为224),对于IPC为1的后续代码,最大的好处是可以进行200多个空闲工作周期。在200个周期中,您可以在10 GB的空间中复制大约800字节/ s,因此对于该尺寸的副本,您可以获得接近于副本成本的免费工作(以使副本免费的方式)。

但是,随着副本大小变得更大,此操作的相对重要性迅速降低(例如,如果您要复制80 KB,则免费工作仅占副本成本的1%)。尽管如此,对于中等大小的副本还是很有意思的。

复制循环也不会完全阻止后续指令的执行。英特尔没有详细说明收益的大小,或收益最大的是哪种副本或周围的代码。(热或冷的目标或源,高ILP或低ILP高延迟代码之后)。

代码大小

与典型的优化memcpy例程相比,执行的代码大小(几个字节)微乎其微。如果性能完全受i缓存(包括uop缓存)未命中的限制,则减小代码大小可能会有所帮助。

同样,我们可以根据副本的大小限制此收益的大小。我不会真的工作了数字,但直觉是用B减小动态代码大小字节最多可以保存C * B缓存缺失,对于某一常数C.每次通话memcpy一次即被高速缓存未命中成本(或收益),但是更高的吞吐量的优势取决于复制的字节数。因此,对于大型传输,较高的吞吐量将主导缓存效果。

再说一次,这并不是一个简单的基准测试,毫无疑问,整个循环无疑将适合uop缓存。您需要进行真实的现场测试来评估这种效果。

特定于架构的优化

您报告说在硬件上rep movsb比平台慢得多memcpy。但是,即使在这里,也有报道说早期硬件(例如Ivy Bridge)的结果相反。

这是完全合理的,因为似乎字符串移动操作会定期获得爱慕-但并非每一代人都如此,因此它可能会比以前的架构更快或更短(至少在其他优势下可能会胜出)。是最新的,但在随后的硬件中却落后。

引用安迪·格鲁(Andy Glew),在P6上实现这些知识后,他应该对此了解一两件事:

在微代码中执行快速字符串的最大弱点是,微代码不适合每一代人,而且越来越慢,直到有人修正它。就像图书馆的人复制不合时宜。我想,错失的机会之一可能是在它们可用时使用128位加载和存储,依此类推。

在这种情况下,可以将其视为另一种“特定于平台的”优化,以应用于memcpy标准库和JIT编译器中发现的典型的书中技巧:但仅适用于更好的体系结构。对于JIT或AOT编译的东西,这很容易,但是对于静态编译的二进制文件,这确实需要特定于平台的调度,但是这种调度通常已经存在(有时在链接时实现),或者mtune可以使用参数来做出静态决策。

简单

即使在似乎已经落后于绝对最快的非时间性技术的Skylake上,它仍然比大多数方法都快,而且非常简单。这意味着更少的验证时间,更少的神秘错误,更少的时间调整和更新Monstermemcpy实现(或者,相反,如果依赖的话,对标准库实现者异想天开的依赖性也更少)。

延迟绑定平台

存储器吞吐量限制算法9实际上可以在两个主要的总体机制下运行:DRAM带宽限制或并发/等待时间限制。

第一种模式是您可能熟悉的模式:DRAM子系统具有一定的理论带宽,您可以根据通道数,数据速率/宽度和频率很容易地计算出理论带宽。例如,我的2通道DDR4-2133系统的最大带宽为2.133 * 8 * 2 = 34.1 GB / s,与ARK上报告的相同。

您在套接字上的所有内核上添加的DRAM所提供的速率将不会超过该速率(通常由于各种低效率而通常会更低)(即,这是单插槽系统的全局限制)。

另一个限制是由一个内核实际上可以向内存子系统发出多少个并发请求所施加的。想象一下,对于一个64字节的高速缓存行,如果一个核心一次只能处理1个请求-当请求完成时,您可以发出另一个请求。还假设非常快的50ns内存延迟。然后,尽管DRAM带宽为34.1 GB / s,但实际上您只能得到64字节/ 50 ns = 1.28 GB / s,或不到最大带宽的4%。

实际上,核心一次可以发出多个请求,但不能无限地发出。通常可以理解,在L1和其余存储器层次结构之间,每个内核只有10个行填充缓冲区,在L2和DRAM之间可能只有16个左右的填充缓冲区。预取会争夺相同的资源,但至少有助于减少有效延迟。有关更多详细信息,请参阅Bandwidth博士就该主题撰写的任何出色文章,主要是在Intel论坛上。

不过,近期的CPU都受限于这个因素,而不是RAM的带宽。通常,它们每个内核达到12-20 GB / s,而RAM带宽可能为50+ GB / s(在4通道系统上)。只有最近的第2代“客户端”内核似乎具有更好的内核,也许更多的行缓冲区可以达到单个内核的DRAM限制,而我们的Skylake芯片似乎就是其中之一。

当然,现在有理由说明英特尔设计的系统具有50 GB / s的DRAM带宽,而由于并发限制,每个内核只能维持<20 GB / s的速度:前者的限制是套接字范围的,而后者是每个内核的。因此,一个8核系统上的每个核可以推送20 GB / s的请求,这时它们将再次受到DRAM的限制。

为什么我要继续这样?因为最佳的memcpy实现通常取决于您所使用的机制。一旦您受到DRAM BW的限制(因为我们的芯片显然是,但大多数不是单个核),使用非临时写入就变得非常重要,因为这样可以节省时间。读取所有权通常会浪费您带宽的1/3。您可以在上面的测试结果中看到:使用NT存储的memcpy实现丢失了其带宽的1/3。

但是,如果您的并发性受到限制,情况将趋于均衡,有时甚至会逆转。您有可用的DRAM带宽,因此NT存储区无济于事,它们甚至会受到伤害,因为它们可能会增加延迟,因为行缓冲区的切换时间可能比预取将RFO线路引入LLC的情况更长(甚至L2),然后在LLC中完成存储,以有效降低延迟。最后,服务器取消内核的NT存储区往往比客户端存储区(和高带宽)慢得多,这加剧了这种影响。

因此,在其他平台上,您可能会发现NT存储不太有用(至少当您关心单线程性能时),也许 rep movsb地方胜出(如果两全其美)。

确实,这最后一项要求进行大多数测试。我知道NT存储在大多数架构(包括当前服务器架构)上的单线程测试中失去了明显的优势,但我不知道该怎么做rep movsb...

参考文献

上面未集成的其他良好信息来源。

comp.arch调查rep movsb对比选择。关于分支预测的许多很好的注释,以及我经常建议的小块方法的实现:使用重叠的第一次和/或最后一次读/写,而不是尝试仅精确地写入所需的字节数(例如,实现从9到16字节的所有副本,作为两个8字节的副本,最多可以重叠7个字节)。


1大概的目的是将其限制在例如代码大小非常重要的情况下。

2请参见第3.7.5节: REP前缀和数据移动。

3需要注意的关键是,这仅适用于单个指令本身内的各种存储:完成后,存储块仍相对于先前存储和后续存储显得有序。因此,代码可以相对于彼此而不rep movs按顺序查看存储,而不是相对于先前存储或后续存储(这是您通常需要的后一种保证)。仅当将复制目标的末尾用作同步标志而不是单独的存储时,这才是问题。

4请注意,非时间离散存储也避免了大多数订购要求,尽管实际上rep movs有更大的自由度,因为WC / NT存储仍然有一些订购约束。

5这在32位时代的后半部分很普遍,当时许多芯片具有64位数据路径(例如,支持支持64位double类型的FPU )。如今,诸如Pentium或Celeron品牌的“中性化”芯片已禁用了AVX,但大概rep movs微码仍可以使用256b加载/存储。

6例如,由于语言对齐规则,对齐属性或运算符,别名规则或在编译时确定的其他信息。在对齐的情况下,即使无法确定确切的对齐方式,它们也至少能够将对齐检查提升到循环之外,或者消除冗余检查。

7我假设“标准”memcpy选择的是非时间方法,对于这种大小的缓冲区,这很有可能。

8这并不一定很明显,因为可能是这样的情况,即由rep movsb简单垄断产生的uop流会很像显式mov情况。看来并非如此,但是后续指令中的指令可能与微码中的指令混合rep movsb

如图9所示,那些可以发出大量独立内存请求并因此使可用的DRAM到核心带宽饱和的驱动器,memcpy将是发贴子(并且正好与诸如指针追逐之类的纯延迟绑定负载)。


3
@BeeOnRope:这是我的结果;该文件包含系统和编译器信息。它具有ERMS支持,但结果表明它在该系统上没有那么强的竞争力。解释了我很难找到一个成功的测试。另外..您介意在tinymembench中仅对64位对齐的副本进行复制和填充吗?尽管完全适用于此处提出的问题,但它严格来说是实际应用程序中典型用例的一个子集。
Nominal Animal

3
@MaximMasiutin-关于分支预测的讨论可能值得在SO上单独解决一个完整的问题,但简短的答案是尚未披露最新芯片的确切技术,但您可能正在寻找与Intel TAGE非常相似的东西和AMD的感知器。一般来说,我只建议您阅读Agner的指南1、2和3 。
BeeOnRope

2
精确的行为通常无关紧要:只是假设除非分支顺序遵循某种简单的(ish)重复模式,否则预测变量将简单地预测其最常看到的方向,因此您需要支付约20个周期分支机构每次采用“其他”方式都会受到惩罚。您可以轻松地检查每个分支的实际表现与您的应用程序perf stat,并perf record -e branch-misses:pp在Linux(和任何等效的是在Windows上)。
BeeOnRope'5

3
@PeterCordes-是的,我似乎movsd与vs并不一致movsb,在某些地方声称它们在erms平台上具有相同的性能,但最重要的是,我说在带有ERMSB的较早拱门上movsb进行的较早测试显示,其执行速度比快得多movsd。这足够具体,以至于我必须已经看到了数据,但是在此线程中找不到它。它可能来自RWT上的 两个大线程之一,或者可能构成了英特尔手册中的示例。
BeeOnRope

2
例如,英特尔手册具有图3-4。Memcpy的最大长度为2KB的性能比较表明,在Ivy Bridge上rep movsd(加上movsb最后三个字节的尾部)比movsb在256个字节上缩放要差得多,此时的斜率似乎是相同的。有一些Ivy Bridge的结果在这里,这显示出rep movsd约3%,比慢rep movsb,但也许是计量误差范围内的,而不是大的即使不是。
BeeOnRope

12

增强型REP MOVSB(Ivy Bridge和更高版本)#

Ivy Bridge微体系结构(处理器分别于2012年和2013年发布)推出了增强型REP MOVSB(我们仍然需要检查相应的位),并允许我们快速复制内存。

较便宜的后期处理器版本-2017年发布的Kaby Lake Celeron和Pentium,没有本来可以用于快速内存复制的AVX,但仍具有增强的REP MOVSB。在2018年及以后发布的一些英特尔移动和低功耗架构中,不是基于SkyLake的,使用REP MOVSB在每个CPU周期中复制的字节数大约增加两倍。

如果块大小至少为256字节,则REP MOVSB(ERMSB)仅比AVX复制或通用寄存器复制快。对于64字节以下的块,它慢得多,因为ERMSB内部启动很高,大约需要35个周期。

请参阅《英特尔优化手册》第3.7.6节“增强的REP MOVSB和STOSB操作(ERMSB)” http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia- 32-architectures-optimization-manual.pdf

  • 启动成本为35个周期;
  • 源地址和目标地址都必须与16字节边界对齐;
  • 源区域不应与目标区域重叠;
  • 长度必须是64的倍数才能产生更高的性能;
  • 方向必须是向前的(CLD)。

如前所述,REP MOVSB在长度至少为256个字节时开始优于其他方法,但是要想明显优于AVX复制,则长度必须大于2048个字节。另外,应注意的是,仅使用AVX(256位寄存器)或AVX-512(512位寄存器)进行内存复制有时可能会产生不良后果,例如AVX / SSE过渡惩罚或降低Turbo频率。因此,REP MOVSB是比AVX更安全的复制内存的方法。

有关REP MOVSB与AVX复制的对齐效果,《英特尔手册》提供以下信息:

  • 如果源缓冲区未对齐,则对ERMSB实现的影响与对128位AVX的影响类似;
  • 如果目标缓冲区未对齐,则对ERMSB实现的影响可能会降低25%,而相对于16字节对齐的方案,memcpy的128位AVX实现可能只会降低5%。

我已经在64位以下的Intel Core i5-6600上进行了测试,并将REP MOVSB memcpy()与简单的MOV RAX [SRC]进行了比较;MOV [DST],当数据适合L1高速缓存时实现RAX :

REP MOVSB memcpy():

MOV RAX ... memcpy():

因此,即使在128位块上,REP MOVSB也比循环中的简单MOV RAX复制(未展开)要慢。仅从256字节块开始,ERMSB的实现就开始超过MOV RAX循环。

#Nehalem及更高版本上的正常(未增强)REP MOVS#

令人惊讶的是,尚未具有增强型REP MOVB的以前的体系结构(Nehalem和更高版本)具有用于大型块的相当快的REP MOVSD / MOVSQ(但不是REP MOVSB / MOVSW)实现,但大小不足以扩大L1缓存的大小。

英特尔优化手册(2.5.6 REP字符串增强)提供了与Nehalem微体系结构相关的以下信息-2009和2010年发布的Intel Core i5,i7和Xeon处理器。

REP MOVSB

如果ECX <4,则MOVSB的等待时间为9个周期。否则,ECX> 9的REP MOVSB的启动成本为50个周期。

  • 小字符串(ECX <4):REP MOVSB的延迟为9个周期;
  • 小字符串(ECX在4到9之间):英特尔手册中没有官方信息,可能超过9个周期但少于50个周期;
  • 长字符串(ECX> 9):50个周期的启动成本。

我的结论是:REP MOVSB在Nehalem上几乎没有用。

MOVSW / MOVSD / MOVSQ

引用《英特尔优化手册》(2.5.6 REP字符串增强):

  • 短字符串(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个周期的启动成本,并且每个字/双字/ qword中数据移动的迭代需要一个周期。

英特尔在这里似乎并不正确。从上面的引用中我们可以理解,对于非常大的内存块,REP MOVSW与REP MOVSD / MOVSQ一样快,但是测试表明,只有REP MOVSD / MOVSQ很快,而REP MOVSW在Nehalem和Westmere上甚至比REP MOVSB慢。 。

根据英特尔手册中提供的信息,在以前的英特尔微体系结构(2008年前)上,启动成本甚至更高。

结论:如果只需要复制适合L1缓存的数据,那么只需4个周期即可复制64字节的数据,这是极好的选择,而且您无需使用XMM寄存器!

#REP MOVSD / MOVSQ是通用解决方案,如果数据适合L1高速缓存,那么它在所有Intel处理器上都非常适用(不需要ERMSB)#

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

约拿(2006-2008)

尼哈拉姆(2009-2010)

韦斯特米尔(2010-2011)

常春藤桥(2012-2013)-带有增强的REP MOVSB(所有后续CPU都具有增强的REP MOVSB)

天湖(2015-2016)

卡比湖(2016-2017)

坎农湖,移动电话(2018年5月-2020年2月)

级联湖,服务器(2019年4月)

Comet Lake,台式机,工作站,移动设备(2019年8月)

流动冰湖(2019年9月)

Tremont,低功率(2020年9月)

老虎湖,可移动(2020年10月)

如您所见,REP MOVS的实现在一个微体系结构与另一个微体系结构之间有很大不同。在某些处理器上,例如Ivy Bridge-REP MOVSB是最快的,尽管比REP MOVSD / MOVSQ快一点,但是毫无疑问,自Nehalem以来,在所有处理器上,REP MOVSD / MOVSQ都可以很好地工作-您甚至不需要“增强的REP MOVSB”,因为在具有增强的REP MOVSB的Ivy Bridge(2013)上,REP MOVSD的每时钟数据字节数与没有增强的REP MOVSB的Nehalem(2010)相同,而实际上REP MOVSB仅自SkyLake(2015)以来发展非常快-是常春藤桥的两倍。因此,CPUID中的此增强的REP MOVSB位可能令人困惑-仅显示其REP MOVSB本身是可以的,但不能表明REP MOVS*速度更快。

最令人困惑的ERMBSB实现是在Ivy Bridge微体系结构上。是的,在非常老的处理器上,在ERMSB之前,大块的REP MOVS *确实使用了常规代码不可用的缓存协议功能(无RFO)。但是,该协议不再用于具有ERMSB的Ivy Bridge。根据安迪·格莱(Andy Glew)对“为什么复杂的memcpy / memset为什么优越?”的回答的评论。彼得·科德斯的答案,较早的处理器曾经使用过常规代码无法使用的缓存协议功能,而Ivy Bridge上不再使用。并解释了为什么REP MOVS *的启动成本如此之高:“选择和设置正确方法的大量开销主要是由于缺乏微代码分支预测”。还有一个有趣的注释,奔腾Pro(P6)在1996年实现了REP MOVS *,具有64位微代码加载和存储以及无RFO缓存协议-与Ivy Bridge中的ERMSB不同,它们没有违反内存顺序。

免责声明

  1. 此答案仅与源数据和目标数据适合L1缓存的情况有关。根据情况,应考虑内存访问(高速缓存等)的特殊性。在某些情况下,预取和NTI可能会提供更好的结果,尤其是在尚未具有增强型REP MOVSB的处理器上。即使在这些较旧的处理器上,REP MOVSD可能也使用了常规代码不可用的缓存协议功能。
  2. 此答案中的信息仅与Intel处理器有关,与与其他制造商(如AMD)可能对REP MOVS *指令的实现有好有坏的处理器无关。
  3. 为了确认起见,我已经给出了SkyLake和Kaby Lake的测试结果-这些体系结构具有相同的每指令周期数据。
  4. 所有产品名称,商标和注册商标均为其各自所有者的财产。

2
有趣的L1D中型缓冲区数据。但是,这可能不是全部。ERMSB的某些好处(例如,商店排序较弱)只会在不适合高速缓存的较大缓冲区中显示出来。即使是常规的快速字符串rep movs也应该使用no-RFO协议,即使在EMSB之前的CPU上也是如此。
彼得·科德斯

3
如果我正确理解的话,您只是从instlatx64结果中抓取了仅L1D的数字。因此结论是movsbmovsdmovsq履行约上所有近期同英特尔平台。最有趣的收获可能是“不要使用movsw”。您无需将其与显式mov指令循环(包括在64位平台上的16字节移动,保证可以使用)进行比较,在许多情况下,循环速度可能会更快。您不知道显示在AMD平台上发生什么,也不知道何时大小超过L1大小。
BeeOnRope

2
最后,您应该注意,除了rep movsb实际实现之外memcpy(没有一个实现memmove),因此您需要其他变体的额外代码。这仅在小尺寸时才重要。
BeeOnRope

1
是的,那句话正是我所指的。
彼得·科德斯

1
@MaximMasiutin-您从哪里获得ERMSB不再使用常规代码不可用的no-RFO协议?当然,至少对于大副本,它肯定仍使用非RFO协议,因为它获得的性能实际上只有使用非RFO才可能实现(这对于stosb它来说是最明显的,但也适用于mov变体)。这是否仍对“常规代码不可用”尚有待商since,因为您在NT存储区中会获得几乎相同的效果,因此,尚不清楚“常规代码不可用”是否仅表示在非NT平台上的NT存储区拥有它们,或NT商店以外的其他东西。
BeeOnRope

8

您说您想要:

显示ERMSB何时有用的答案

但是我不确定这意味着您认为的含义。查看链接到的3.7.6.1文档,它显式地表示:

使用ERMSB实施memcpy可能无法达到使用256位或128位AVX替代方案所达到的吞吐量水平,具体取决于长度和对齐因子。

因此,仅因为CPUID表明支持ERMSB,所以不能保证REP MOVSB是复制内存的最快方法。这只是意味着它不会像以前的某些CPU那样糟糕。

但是,仅因为在某些条件下可能存在某些替代方法可以运行得更快,并不意味着REP MOVSB是没有用的。现在,该指令所引起的性能损失已经消失,它可能再次成为有用的指令。

请记住,与我见过的一些涉及更多的memcpy例程相比,这只是一小段代码(2个字节!)。由于加载和运行大量代码也有一定的代价(将其他一些代码从cpu的缓存中抛出),因此有时AVX等的“好处”将被其对您其余部分的影响所抵消。码。取决于您在做什么。

您还问:

为什么REP MOVSB的带宽这么低?我该怎么做才能改善它?

“做某事”以使REP MOVSB更快地运行是不可能的。它会做的。

如果您希望从memcpy中看到更高的速度,则可以为其寻找源。它在某处。或者,您可以从调试器中跟踪它,并查看实际的代码路径。我的期望是它使用某些AVX指令一次处理128位或256位。

或者您可以...好吧,您要求我们不要说。


我测试REP MOVSB了L3缓存中的大小,这确实与SSE / AVX解决方案具有竞争力。但是我还没有发现它明显更好。对于大于L3缓存的大小,非临时存储仍然可以赢得大量时间。关于代码大小的观点很有趣,值得考虑。我对微码不太了解。REP MOVSB由于使用微代码实现了“微指令”,因此即使它不占用大量代码高速缓存并且仅算作一条指令,它仍可能会占用许多端口和/或微操作。
Z玻色子

“还没有发现它明显更好。” 比什么更好?“增强”与“最佳”不同。我还没有看到任何地方承诺过它会是表现最好的地方。我不认为这就是那个cpu标志要传达的意思。它比遭受惩罚(甚至在movq / cmp循环上)的平台要好。“代码大小”并不总是那么容易看到。就像存储在高速缓存行中的内存可以交换进出cpu一样,代码也是如此。在巨大的旧内存中分页意味着您的其他一些代码将被逐出。
David Wohlferd '17

请参阅我在问题末尾引用的评论,该评论声称ERMSB即使对于大型存储也应比非临时存储更好。
Z玻色子

1
等待!您有证据表明它rep movsb比其他选择更好吗?我想听听更多。需要澄清的是,我不是在寻找一个仅显示rep movsb大型阵列在哪里更好的答案(无论如何这都是不正确的)。我很想看到任何rep movsb比其他地方更好的例子。
Z玻色子

1
这个答案确实钉住了需要说的话。关键是,memcpy高度优化,做各种疯狂的事情来获得最大的速度成为可能。如果您研究库的实现,您可能会感到惊讶。(除非您使用的是Microsoft的编译器,否则您可能会感到失望,但您不会问这个问题。)极不可能memcpy在速度上击败手动调整的函数,如果可以,那么在为Ivy Bridge或支持这些增强功能的任何体系结构进行调整时,Glibc人士也很有可能会切换到该版本。
科迪·格雷

7

这不是对所陈述问题的解答,仅是试图找出答案时的我的结果(和个人结论)。

总之:GCC已经优化memset()/ memmove()/ memcpy()(参见例如GCC /配置/ I386 / i386.c:expand_set_or_movmem_via_rep()中GCC来源;也寻找stringop_algs在相同的文件,以查看架构相关变体)。因此,没有理由通过将自己的变量与GCC一起使用而获得可观的收益(除非您忘记了对齐数据的对齐属性之类的重要内容,或者未启用诸如此类的足够具体的优化-O2 -march= -mtune=)。如果您同意,那么所陈述问题的答案在实践中或多或少无关紧要。

(我只希望有一个memrepeat()memcpy()相比的反面memmove(),它将重复缓冲区的初始部分以填充整个缓冲区。)


我目前正在使用一台Ivy Bridge计算机(Core i5-6200U笔记本电脑,Linux 4.4.0 x86-64内核,带有ermsin/proc/cpuinfo标志)。因为我想确定是否可以找到基于memcpy()的自定义变量rep movsb优于直接变量的情况memcpy(),所以我编写了一个过于复杂的基准测试。

其核心思想是主程序分配三个大的存储区:originalcurrent,和correct,每个大小完全一样,和至少页对齐。复制操作分为几组,每组具有不同的属性,例如所有源和目标都对齐(对齐到一定数量的字节),或者所有长度都在同一范围内。每组使用的阵列描述srcdstn三重峰,其中所有srcsrc+n-1dstdst+n-1完全内current区。

一个Xorshift * PRNG用来初始化original随机数据。(就像我上面的警告,这是过于复杂,但我想确保我不会留下任何方便快捷的编译器。)将correct通过与开始得到的面积original数据current,将所有的三胞胎在当前的设置,使用memcpy()提供C库,然后将该current区域复制到correct。这样可以验证每个基准功能是否正常运行。

每组复制操作都使用相同的功能计时很多次,并且将这些操作的中值用于比较。(在我看来,中值在基准测试中最有意义,并且提供了合理的语义-该功能至少在一半的时间内速度至少如此之快。)

为了避免编译器优化,我让程序在运行时动态加载函数和基准。这些函数都具有相同的形式,void function(void *, const void *, size_t)-请注意,与memcpy()和不同的是memmove(),它们什么也不返回。基准测试(命名为复制操作的集合)是通过函数调用(将指针指向current区域及其大小作为参数等)动态生成的。

不幸的是,我还没有找到任何集

会击败

使用 gcc -Wall -O2 -march=ivybridge -mtune=ivybridge使用GCC 5.4.0上前述核心i5-6200U膝上型运行Linux-4.4.0 64位内核。但是,复制4096字节对齐且大小合适的块的操作即将完成。

这意味着至少到目前为止,我还没有发现使用 rep movsbmemcpy变体会有意义的情况。这并不意味着不存在这种情况。我只是没有找到一个。

(在这一点上,代码是一团意大利面条,我感到羞耻而不是为之骄傲,因此,除非有人提出要求,否则我将省略发布源代码。但是,上面的描述应该足以编写更好的代码。)


不过,这并不令我感到惊讶。C编译器可以推断出许多有关操作数指针的对齐方式以及要复制的字节数是否为编译时常数(合适的2的倍数的倍数)的信息。该信息可以并且应该被编译器用来用其自己的C库memcpy()/memmove()函数替换。

GCC正是这样做的(请参见GCC源代码中的gcc / config / i386 / i386.c:expand_set_or_movmem_via_rep();也请stringop_algs在同一文件中查找与体系结构相关的变体)。事实上,memcpy()/ memset()/memmove()已分别对相当多的x86处理器类型而优化的; 如果GCC开发人员还没有提供erms支持,我将感到非常惊讶。

GCC提供了几个函数属性,开发人员可以使用这些属性来确保生成良好的代码。例如,alloc_align (n)告诉GCC该函数返回的内存至少与n字节对齐。应用程序或库可以通过创建“解析器函数”(返回函数指针)并使用ifunc (resolver)属性定义函数来选择在运行时使用哪个函数实现。

为此,我在代码中使用的最常见的模式之一是

ptr某个指针在哪里,alignment是它对齐的字节数;然后,GCC知道/假定pointeralignment字节对齐。

尽管很难正确使用,但另一个有用的内置函数是__builtin_prefetch()。为了最大程度地提高整体带宽/效率,我发现最小化每个子操作中的延迟可以产生最佳结果。(对于将分散的元素复制到连续的临时存储中,这是困难的,因为预取通常会涉及整个缓存行;如果预取的元素太多,则大部分的缓存会由于存储未使用的项目而浪费。)


i5-6200U笔记本电脑不是Ivy Bridge。是Skylake。在Ivy Bridge系统上看到tingybenchmark会很有趣。
Z玻色子

@Zboson:非常 正确;感谢您指出。我不知道我从哪儿得到这个假设。可能是从我后面拉的。哎哟。确实也解释了我的结果。
名义动物

4

有许多更有效的方式来移动数据。如今,的实现memcpy会从编译器生成特定于体系结构的代码,这些代码会根据数据的内存对齐方式和其他因素进行优化。这样可以更好地使用x86世界中的非临时性缓存指令和XMM及其他寄存器。

当您进行硬编码时,rep movsb可以避免使用内在函数。

因此,对于诸如的东西memcpy,除非您要编写与非常特定的硬件相关联的东西,并且除非您要花时间memcpy在汇编中(或使用C级内在函数)编写高度优化的函数,否则您将迄今为止最好让编译器弄清楚你。


1
实际上,如果使用增强型rep movsb,则使用rep movsd的速度会较慢。在写这样的答案之前,请先阅读此功能的含义。
fuz

1
我在memcpy 这里讨论了一个习俗。一个评论是“请注意,在Ivybridge和Haswell上,如果缓冲区较大以适合MLC,则可以使用rep movsb击败movntdqa; movntdqa将RFO引入LLC,rep movsb则不然。” 我能得到的东西不如memcpymovntdqa。我的问题是,我该如何做得更好或更好rep movsb
Z玻色子

2
这主要是为了教育。我正在尝试了解ERMSB。最终目标是从主内存中获得最大的带宽。我提供了我使用的问题代码。这就是我要做的。
Z玻色子

3
这个答案似乎与ERMSB之类的“快速字符串移动”指令的实际情况脱节,并且重复了谬论,即对于性能最高的代码,您应该让编译器为您解决。现在,对于大多数代码和大多数开发人员来说,要获得高性能代码,您应该让编译器为您解决这个问题,但是几乎总有一个层次,一个精通细节的人可以使它更快(例如,因为他们更了解数据的形状等)。这个问题属于这一类,因为它明确提到的快速串OPS等
BeeOnRope

4
@fuz:实际上,在目前所有实现ERMSB的CPU上,rep movsd速度显然也很快。(即使您说对了,英特尔只记录了适用于rep movsdb/的ERMSB stosb
彼得·科德斯

1

作为一般memcpy()指导:

a)如果要复制的数据很小(可能少于20个字节)并且具有固定大小,请让编译器执行。原因:编译器可以使用正常mov指令并避免启动开销。

b)如果要复制的数据很小(小于约4 KiB)并且保证对齐,请使用rep movsb(如果支持ERMSB)或rep movsd(如果不支持ERMSB)。原因:在复制任何内容之前,使用SSE或AVX替代方法会产生大量的“启动开销”。

c)如果要复制的数据很小(小于约4 KiB)并且不能保证对齐,请使用rep movsb。原因:使用SSE或AVX或rep movsd大量使用SSE或AVX,或者rep movsb在开始或结束时使用一些,开销太大。

d)对于其他所有情况,请使用以下内容:

原因:这太慢了,以至于它将迫使程序员寻找一种不涉及复制大量数据的替代方法。由于避免了复制大量数据,因此最终的软件将大大加快运行速度。


2
“在复制任何内容之前,使用SSE或AVX替代品会产生大量的”启动开销”。” 您提到的大量启动开销是多少?您能否提供更多详细信息?
Z玻色子

2
@Zboson:检查起始地址是否正确对齐(对于源地址和目标地址),检查大小是否为rep movsb整数倍,检查是否仍应使用,等等(均存在潜在的分支错误预测)。对于大多数CPU,在不使用SSE / AVX时会关闭电源以节省电量,因此您可能会受到“ SSE / AVX开启延迟”的影响。然后是函数调用开销(过于膨胀以至于内联),其中可能包括保存/恢复调用者正在使用的所有SSE / AVX寄存器。最后,如果没有其他东西使用SSE / AVX,则在任务切换期间会额外保存/恢复SSE / AVX状态。
布伦丹

1
@Zboson:也;如果人很聪明,他们就会有多种变化,如memcpy_small()memcpy_large_unaligned()memcpy_large_aligned()等,这将有助于摆脱一些启动开销(检查等)。不幸的是,人们比聪明更懒惰,而且(据我所知)没有人真正做到这一点。
布伦丹

2
@BeeOnRope:因为我不同意这个答案,所以两个评论都针对@ Brendan。很抱歉给您带来混乱,如果您有兴趣在前面的评论中看到您正在谈论的示例,那么向量memcpy的启动开销很低,请您与我们联系。我不愿不同意您的发言。
彼得·科德斯

2
@CodyGray-实际上,最新硬件上的rep movsbrep movsd(和rep movsq)对齐注意事项基本相同。当然,rep movsb 从概念上讲,它适用于字节,但是在所有的字符串移动指令的幕后,它们都试图移动较大的字节块,因此它们都受益于更好的对齐方式(这种有益的对齐方式通常为16、32或64个字节,因此并不真正相关)到操作的原始大小)。这类似于memcpy实现通常从对齐中受益的方式,即使它们在概念上对字节起作用。
BeeOnRope
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.