我在嵌入式系统中编写 memset 函数,我发现最快的方法是使用movups
。鉴于我的内存已经对齐,我决定使用movaps
以获得更快、更小的结果。经过多次尝试,它们都给出了完全相同的系统时间,并且它们都具有相同的操作码大小。那么如果它需要对齐的内存并且仍然只提供相同的性能,那有什么意义呢movaps
?
来自英特尔开发人员手册:
MOVAPS:在 XMM 寄存器之间或 XMM 寄存器和内存之间移动四个对齐的打包单精度浮点值。
MOVUPS:在 XMM 寄存器之间或 XMM 寄存器和内存之间移动四个未对齐的打包单精度浮点值。
同样,MOVAPD、MOVUPD、MOVDQA、MOVDQU 的用途是什么(实际上它们都在做同样的事情)
reddit 中的问题:
3
最佳答案
1
如今,唯一剩下的用途就是确保assert
数据确实对齐。缓存行拆分,尤其是页面拆分,速度并不快,因此您可能需要验证对齐,而不是让硬件以比预期更慢的速度进行访问。
从历史上看(在 Nehalem 和 Bulldozer 系列之前,大约 2008 年),movups
即使对于对齐的地址,速度也总是较慢,例如解码为更多的 uops 并且吞吐量更差,即使前端没有瓶颈也是如此。(AMD K10 具有高效的movups
加载但不具备存储能力。)请参阅以获取包含那些旧 CPU 的指令表(uops 和吞吐量)。
SSE1 是 Pentium III 中的新功能,于 1999 年推出,并在之前的几年中在纸面上设计,当时晶体管预算要少得多。处理 32 位未对齐的加载/存储并发挥全部性能(只要它们不分散在缓存行中)是他们可以做到的,但他们不想将晶体管花在 128 位加载上,因为这将占用 4 倍多路复用器的宽度。
在 SIMD 的早期,它也没有被广泛使用,因此(对于用户而言)对 CPU 的好处不会那么大。在 x86-64 中,SSE2 是所有软件都可以采用的基本功能,但大多数 32 位代码不能。
在早期,编译器自动矢量化受到很大限制,因此只有少数程序或库具有手动矢量化代码。由于 SIMD 大多只用于围绕其设计的程序,因此对齐要求通常不是一个很大的限制。
请注意,使用 C 内部函数,_mm_load_ps
而不是_mm_loadu_ps
允许编译器将负载折叠到其他指令(如)的内存源操作数中addps xmm0, [rdi]
。与 AVX()不同,对齐要求是 SSE 的默认要求vaddps xmm0, xmm0, [rdi]
。使用 AVX,将对齐保证传达给编译器仅对调整选择有用(尤其是,在 Sandybridge 过时很久之后仍然支持它)。但内部函数与movaps
asm 指令是分开的,具有目的。
movaps
一些现代编译器,MSVC 和 ICC-classic,除了复制寄存器外,从不使用。它们movups
使用_mm_load_ps
/store
除非它们将负载折叠到另一条指令的内存源操作数中,所以这是您真正进行对齐检查的唯一情况。他们很久以前就开始这样做了,那时 Core 2 系统还没有完全过时;他们制作的二进制文件在那些旧系统上速度较慢。
5
-
1他们是如何让 movup 和现代计算机中的 movap 一样快的?
– -
1@EgemenYalın:请参阅 – 在 Intel 上,如果加载结果是缓存行拆分或缓存未命中,则依赖于该加载的 uops 将被重放。因此,在正常情况下,调度程序会乐观地将依赖的 uops 调度到执行端口,以应对快速情况,例如,对于 XMM 加载,L1d 缓存命中会在 5 个周期内产生加载结果。
– -
1在缓存行拆分时,加载执行单元需要执行另一次缓存访问(它们具有“拆分缓冲区”来保存数据的前半部分,以便后面的周期可以产生完整的加载结果。)这需要将数据从缓存中移位一定数量的字节,例如桶式移位器,它仅支持 8 的倍数移位计数,因此可以跳过最后 3 级多路复用器。与处理缓存行内的未对齐情况类似,需要进行一些移位(有效地进行多路复用以选择哪些缓存字节最终出现在哪些加载结果字节中)。
– -
x86 CPU 已经针对较窄的加载实现了此功能,这就是它们能够方式。(在 AMD 上,任何未对齐的字节/字/双字都在 8 字节自然对齐的 qword 内。在 Intel 上,任何未对齐的加载/存储最多可在整个缓存行内达到 8 个字节,因此为 64 个字节。仅适用于可缓存的内存区域;对于不可缓存的内存区域有更严格的要求。)
– -
@EgemenYalın:同样相关: – 即使是 GPR 加载/存储在非 x86 ISA 上未对齐时通常也会更慢。
–
|
–
–
MOVU*
除其他指令外,还存在这些指令以允许非对齐访问(如果需要)。–
|