指令rep stosb
的执行速度如何能比这段代码快?
Clear: mov byte [edi],AL ; Write the value in AL to memory
inc edi ; Bump EDI to next byte in the buffer
dec ecx ; Decrement ECX by one position
jnz Clear ; And loop again until ECX is 0
在所有现代CPU上都能保证这一点吗?我应该总是喜欢使用rep stosb
而不是手动编写循环吗?
2条答案
按热度按时间wrrgggsh1#
在现代CPU中,
rep stosb
和rep movsb
的微编码实现实际上使用的存储区比1B宽,因此它可以比每时钟一个字节快得多。(请注意,这 * 仅 * 适用于stos和mov,不适用于
repe cmpsb
或repne scasb
。不幸的是,它们仍然很慢,与Skylake相比,最多每字节2个周期,这与AVX 2vpcmpeqb
相比在实现memcmp或memchr方面是可悲的。有关指令表和the x86 tag wiki中的其他性能链接,请参见https://agner.org/optimize/。有关gcc不明智地内联
repnz scasb
或对碰巧变大的strlen
执行不太坏的标量bithack的示例,以及一个简单的SIMD替代方案,请参见为什么启用优化后此代码的速度会慢6.5倍。)rep stos/movs
具有显著的启动开销,但对于大型memset/memcpy而言,它的性能提升良好。(请参阅Intel/AMD的优化手册,了解何时使用rep stos
与矢量化循环来处理小缓冲区。)但是,如果没有ERMSB特性,rep stosb
针对中小型内存集进行了调整,最好使用rep stosd
或rep stosq
(如果您不打算使用SIMD循环)。当使用调试器单步调试时,
rep stos
只执行一次迭代(ecx/rcx的一次递减),因此微码实现永远不会开始,不要让这欺骗了您,以为这就是它所能做的一切。有关英特尔P6/SnB系列微体系结构如何实施
rep movs
的详细信息,请参阅What setup does REP do?。请参阅Enhanced REP MOVSB for memcpy,了解
rep movsb
与SSE或AVX循环在具有ERMSB功能的英特尔CPU上的内存带宽考虑因素。(特别注意,由于对同时运行的缓存未命中数量的限制,以及RFO与非RFO存储协议的限制,多核至强CPU无法仅使用单个线程就使DRAM带宽饱和。)一个现代的英特尔CPU应该在每个时钟周期内运行一次问题中的asm循环,但是AMD推土机系列的内核可能甚至不能在每个时钟周期内管理一次存储(瓶颈在于处理inc/dec/分支指令的两个整数执行端口。如果循环条件是
edi
上的cmp/jcc,AMD内核可能会对比较和分支进行宏融合)。所谓的快速字符串操作(Intel P6和SnB系列CPU上的
rep movs
和rep stos
)的一个主要特性是,当存储到先前未缓存的内存时,它们避免了读取所有权缓存一致性流量。因此,这就像使用NT存储写入整个缓存行,但仍然是强有序的。(ERMSB特性确实使用弱有序存储)。IDK AMD的实现有多好。
(And a更正:我之前说过,英特尔SnB只能处理每2个时钟周期一次的执行分支吞吐量,但实际上它可以在每1个时钟周期一次迭代的情况下运行微型循环。)
查看x86标签wiki上链接的优化资源(特别是Agner Fog的指南)。
Intel IvyBridge以及更高版本的ERMSB,允许
rep stos[b/w/d/q]
和rep movs[b/w/d/q]
使用弱序存储(如movnt
),允许存储无序提交到缓存。如果不是所有的目的地已经在L1缓存中处于热状态,这是一个优势。我相信,从文档的措辞来看,在快速字符串操作的末尾存在隐式内存障碍,所以任何重新排序只在字符串op所做的存储之间可见,而在它和其他存储之间不可见。也就是说,在 *rep movs
之后仍然不需要sfence
*。所以对于Intel IvB和更高版本上的大型对齐缓冲区,
memset
的rep stos
实现可以击败任何其他实现。使用movnt
存储(不将数据留在缓存中)的实现也应该接近主内存写带宽饱和,但实际上可能无法跟上。请参见评论以了解对此的讨论,但我无法找到任何数字。对于小缓冲区,不同的方法有非常不同的开销量。微基准测试可以使SSE/AVX复制循环看起来比它们更好,因为每次使用相同的大小和对齐进行复制可以避免启动/清理代码中的分支预测错误。IIRC,建议在Intel CPU上对128 B以下的复制使用矢量化循环(不是
rep movs
)。阈值可能高于此值,具体取决于CPU和周围代码。Intel的优化手册也讨论了不同memcpy实现的开销,
rep movsb
比movdqu
有更大的未对齐代价。请参阅优化的memset/memcpy实现的代码,了解更多关于实际操作的信息(例如Agner Fog的库)。
gtlvzcf82#
如果您的CPU具有CPUID ERMSB位,则
rep movsb
和rep stosb
命令的执行方式与旧处理器上的不同。参见Intel Optimization Reference Manual,第3.7.6节增强型REP MOVSB和REP STOSB操作(ERMSB)。
手册和我的测试都表明,在Skylake微架构的32位CPU上,
rep stosb
与通用32位寄存器移动相比的优势只出现在大于128字节的大内存块上。x一米四氮一xjnz Clear
)的速度会快很多,因为rep stosb
的启动成本非常高--大约35个周期。然而,这种速度差异在2019年9月推出的Ice Lake微架构上已经缩小,推出了快速短REP MOV(FSRM)功能。该功能可以通过一个CPUID位进行测试。它原本打算用于128字节和更短的字符串,以便快速,但是,事实上,使用rep movsb时,64字节之前的字符串仍然比使用简单的64位寄存器复制时慢。除此之外,FSRM仅在64位下实现,而不是在32位下实现。至少在我的i7 - 1065G7 CPU上,rep movsb
仅对64位以下的小字符串快速,但是,在32位上,字符串必须至少为4KB,rep movsb
才能开始优于其他方法。要在具有CPUID ERMSB位的处理器上获得
rep stosb
的优势,应满足以下条件:cld
指令设置)。根据英特尔优化手册,当内存块的长度至少为128字节时,ERMSB开始优于Skylake上通过常规寄存器进行的内存存储,正如我所写,有高内部启动ERMSB-大约35个周期,当长度超过2048字节时,ERMSB开始明显优于其他方法,包括AVX复制和填充,然而,这主要适用于Skylake微体系结构,而不一定适用于其它CPU微体系结构。
在某些处理器上,但在另一些处理器上并非如此,当目标缓冲区是16字节对齐时,使用ERMSB的REP STOSB比SIMD方法(即使用MMX或SSE寄存器时)的性能更好。对于基于英特尔微体系结构代码Ivy Bridge的处理器,使用ERMSB的()性能相对于对齐情况可能会降低约20%。根据英特尔的优化手册,当目标未对齐时,REP STOSB的SIMD实现将经历更可忽略的性能下降。
基准
我做过一些测试。代码多次填充同一个固定大小的缓冲区,所以缓冲区停留在缓存中(L1,L2,L3),这取决于缓冲区的大小。迭代的次数是这样的,总执行时间应该是大约2秒。
天空湖
在2015年9月发布的英特尔酷睿i5 6600处理器上,基于Skylake-S四核微架构(3.30 GHz基频,3.90 GHz最大Turbo频率),配备4个32K L1高速缓存、4个256K L2高速缓存和6MB L3高速缓存,我可以在REP STOSB上获得约100 GB/秒的32K块。
使用
REP STOSB
的memset()实现:使用
MOVDQA [RCX],XMM0
的memset()实现:1297920000个16字节的数据块:3.5795秒5532.7798兆字节/秒
0648960000块32字节:55538秒35659727兆字节/秒
1622400000个64字节的数据块:15.7489秒6287.6436兆字节/秒
817587402块127字节:9.6637秒10246.9173兆字节/秒
811200000块128字节:9.6236秒10289.6215兆字节/秒
804911628块129字节:9.4852秒10439.7473兆字节/秒
407190588个255字节的数据块:6.6156秒14968.1754兆字节/秒
405600000个256字节的数据块:6.6437秒14904.9230兆字节/秒
202800000块512字节:5.0695秒19533.2299兆字节/秒
1024字节的101400000个数据块:4.3506秒22761.0460兆字节/秒
3168750个32768字节的数据块:3.7269秒26569.8145兆字节/秒(!),即26 GB/秒
2028000块,51200字节:4.0538秒24427.4096兆字节/秒
413878个250880字节的数据块:3.9936秒24795.5548兆字节/秒
19805块,5242880字节:4.5892秒21577.7860兆字节/秒
请注意,使用XMM0寄存器的缺点是它是128位的(16字节),而我可以使用256位的YMM0寄存器(32字节)。无论如何,
stosb
使用非RFO协议。Intel x86从Pentium Pro开始就有了"快速字符串"P6快速字符串采用REP MOVSB和更大的字符串,并使用64位微码加载和存储以及非RFO高速缓存协议来实现它们。与Ivy Bridge中的ERMSB不同。有关详细信息和源代码,请参见https://stackoverflow.com/a/33905887/6910868。无论如何,即使只比较我提供的两种方法,即使第二种方法远非理想,如您所见,在64位数据块上,
rep stosb
速度较慢,但从128字节数据块开始,rep stosb
开始优于其他方法,从512字节数据块开始,差异非常明显,甚至更长。前提是您在高速缓存中反复清除相同的内存块。因此,对于
REP STOSB
,最大速度为103957(十万三千九百五十七)兆字节每秒,而对于MOVDQA [RCX],XMM0的最大速度仅为26569(二万六千五百六十九)二万六千五百六十九。如您所见,最高性能出现在32K数据块上,这相当于我在其上进行基准测试的CPU的32K L1缓存。
冰湖
REP STOSB与AVX-512存储
我还在2019年8月发布的英特尔i7 1065G7 CPU(Ice Lake/Sunny Cove微架构)上做过测试,主频:1.3 GHz,最高Turbo频率3.90 GHz,支持AVX512F指令集,拥有4个32K一级指令缓存和4个48K数据缓存,4个512K二级缓存和8 MB三级缓存。
目标对齐
在按
rep stosb
归零的32K数据块上,目标未对齐1个字节时的性能为175231 MB/s(例如$7FF4FDCFFFFF),并迅速上升到219464 MB/s,以对齐64字节(例如$7FF4FDCFFFC0),然后对于按256字节对齐的目的地,逐渐上升到222424 MB/秒(对齐256字节,即$7FF4FDCFFF00)。此后,即使目的地对齐32KB(例如$7FF4FDD00000),速度也没有上升,仍然是224850 MB/秒。rep stosb
和rep stosq
之间的速度没有差异。在以32K对齐的缓冲区上,AVX-512存储的速度与
rep stosb
完全相同,对于从循环中的2个存储开始的循环(227777 MB/秒),对于展开4个甚至16个存储的循环,速度没有增长。然而,对于只有1个存储的循环,速度稍低-203145 MB/秒。然而,如果目标缓冲区仅错位1个字节,AVX512的存储速度就会急剧下降,即下降2倍以上,降至93811 MB/秒,而类似缓冲区上的
rep stosb
则为175231 MB/秒。缓冲区大小
rep stosb
(71817 MB/s)快3倍rep stosb
降至38682 MB/s。在此数据块类型下,AVX-512的性能相差5倍。rep stosb
,速度为123207 MB/s,几乎慢了两倍。同样,rep stosb
和rep stosq
之间没有差异。rep stosb
:* * 220515 MB/s**-现在终于!我们正在接近我的CPU的L0数据缓存大小-48Kb!这是每秒220千兆字节!rep stosb
:每秒70395兆字节!rep stosb
开始优于AVX-512存储。rep stosb
的缓存大小为70653 MB/s。这就是rep stosb
开始优于AVX-512的地方。差异还不明显,但缓冲区越大,差异越大。rep stosb
的速度是27412 MB/s,也就是说是AVX-512的两倍!我也尝试过使用非时态指令来填充32K的缓冲区
vmovntdq [rcx], zmm31
,但是性能比vmovdqa64 [rcx], zmm31
慢4倍。在填充内存缓冲区时,我如何利用vmovntdq
的优势?是否应该有一些特定大小的缓冲区,以便vmovntdq
发挥优势?另外,如果目标缓冲区至少对齐64位,则
vmovdqa64
与vmovdqu64
在性能上没有差异。因此,我确实有一个问题:当我们有vmovdqu64
时,指令vmovdqa64
是否仅用于调试和安全?多次清除该高速缓存内同一内存块的性能总结
Ice Lake CPU上的
rep stosb
仅在重复清除大于L0缓存大小(即Intel i7 1065 G7 CPU上的48 K)的相同内存缓冲区时才开始优于AVX-512存储。在较小的内存缓冲区上,AVX-512存储要快得多:对于1 KB-快3倍,对于512字节-快5倍。然而,AVX-512存储易受缓冲区未对齐的影响,而
rep stosb
对未对齐的敏感度较低。因此,我发现
rep stosb
仅在超过L0数据缓存大小(如Intel i7 1065 G7 CPU为48 KB)的缓冲区中开始优于AVX-512存储。此结论至少在Ice Lake CPU上有效。Intel早期的建议是从2KB缓冲区开始字符串复制开始优于AVX复制,也应该针对较新的微架构重新测试。清除不同的内存缓冲区,每个缓冲区仅清除一次
我以前的基准测试是在一行中多次填充同一个缓冲区。一个更好的基准测试可能是分配许多不同的缓冲区,并且只填充每个缓冲区一次,以不干扰该高速缓存。
在这种情况下,
rep stosb
和AVX-512存储之间没有太大区别。唯一的区别是在Windows 10 64位下,所有数据都没有接近物理内存限制。在以下基准测试中,总数据大小低于8 GB,总物理内存为16 GB。当我分配大约12 GB时,性能下降了大约20倍,无论使用什么方法。Windows开始丢弃清除的内存页,而且可能在内存快满的时候做了一些其他的事情。i71065 G7 CPU上8 MB的L3缓存大小似乎根本不影响基准测试。重要的是你没有“我不必接近物理内存限制,这取决于你的操作系统如何处理这种情况。正如我所说,在Windows 10下,如果我只占用一半的物理内存,那还可以,但如果我占用了3/4的可用内存,我的基准测试速度就会慢20倍。我甚至没有尝试占用超过3/4的内存。正如我所说,总内存大小为16 GB。2根据任务管理器,可用内存大小为12 GB。以下是在16 GB总内存的i7 1065 G7单线程CPU上,将总内存为8 GB的各个内存块填充为零(MB/秒)的速度基准。“AVX”指的是“AVX-512”正常存储,“stosb”指的是“rep stosb”。
关于清除该高速缓存中的内存的结论
如果该高速缓存中不存在内存,则当您需要用零填充内存时,AVX-512存储和
rep stosb
的性能大致相同。重要的是缓存,而不是在这两种方法之间进行选择。使用非临时存储清除不在该高速缓存中的内存
我对由一系列64字节对齐的缓冲区分割的6-10 GB内存进行了零化。没有缓冲区会被零化两次。较小的缓冲区会有一些开销,而我只有16 GB的物理内存,所以我使用较小的缓冲区对较少的内存进行了零化。我对缓冲区进行了各种测试,从256字节开始,每个缓冲区最多8 GB。我采用了3种不同的方法:
1.通过
vmovdqa64 [rcx+imm], zmm31
进行正常AVX-512存储(4次存储循环,然后比较计数器);1.通过
vmovntdq [rcx+imm], zmm31
存储非暂时AVX-512(4次存储的相同循环);rep stosb
.对于较小的缓冲区,普通AVX-512存储是赢家。然后,从4KB开始,非临时存储领先,而
rep stosb
仍然落后。然后,从256 KB开始,
rep stosb
的表现优于AVX-512,但非临时存储没有,此后情况没有改变。赢家是非临时AVX-512存储,然后是rep stosb
,然后是普通AVX-512存储。避免AVX-SSE转换损失
对于所有AVX-512代码,我都使用
ZMM31
寄存器,因为SSE寄存器从0到15,所以AVX-512寄存器16到31没有对应的SSE寄存器,因此不会导致转换损失。