C++ SIMD本质:对齐操作与未对齐操作不同?

C++ SIMD本质:对齐操作与未对齐操作不同?,c++,x86,simd,intrinsics,C++,X86,Simd,Intrinsics,我开始学习一些关于SIMD本质的知识。我注意到有些函数有对齐版本和未对齐版本,例如\u mm\u store\u si128和\u mm\u storeu\u si128。我的问题是,这些函数的性能是否不同?如果不是,为什么有两个不同的版本?在较旧的CPU上,对齐和未对齐的加载/存储之间存在很大的性能差异。在较新的CPU上,差异要小得多,但作为“经验法则”,您仍应尽可能选择对齐版本。我要说的是“始终对齐(尽可能)”,这样无论发生什么情况,都可以覆盖您。一些平台不支持未对齐的访问,其他平台的性能将

我开始学习一些关于SIMD本质的知识。我注意到有些函数有对齐版本和未对齐版本,例如
\u mm\u store\u si128
\u mm\u storeu\u si128
。我的问题是,这些函数的性能是否不同?如果不是,为什么有两个不同的版本?

在较旧的CPU上,对齐和未对齐的加载/存储之间存在很大的性能差异。在较新的CPU上,差异要小得多,但作为“经验法则”,您仍应尽可能选择对齐版本。

我要说的是“始终对齐(尽可能)”,这样无论发生什么情况,都可以覆盖您。一些平台不支持未对齐的访问,其他平台的性能将大幅下降。如果您选择对齐访问,在任何情况下都将获得最佳性能。在某些平台上可能会有少量内存成本,但这是非常值得的,因为如果使用SIMD,就意味着要追求性能。我想不出为什么要实现未对齐的代码路径。也许如果你不得不处理一些旧的设计,这些设计并没有考虑到SIDM,但我想说的是,这种可能性微乎其微

我想说,标量也是如此,正确的对齐在任何情况下都是正确的,并且在实现最佳性能时为您节省了一些麻烦


至于为什么未对齐的访问可能较慢甚至不受支持,这是因为硬件的工作方式。假设您有一个64位整数和一个64位内存控制器,如果您的整数正确对齐,内存控制器可以一次性访问它。但是如果它是偏移量,内存控制器将不得不执行2个操作,加上CPU可能需要移动数据以正确组合数据。由于这是次优的,一些平台甚至不隐式地支持它,作为提高效率的手段。

如果数据实际上是对齐的,则未对齐的加载/存储将与对齐的存储具有相同的性能

  • 未对齐的ops:未对齐的数据将导致性能下降,但您的程序仍然有效

  • 对齐的操作:未对齐的数据将导致故障,使您能够检测到意外未对齐的数据,而不是默默地导致性能下降

现代CPU对未对齐的负载有很好的支持,但当负载跨越缓存线边界时,性能仍然会受到很大的影响

使用SSE时,对齐的加载可以作为内存操作数折叠到其他操作中。这稍微提高了代码大小和吞吐量

使用AVX时,这两种负载都可以折叠到其他操作中。(AVX默认行为是允许未对齐的内存操作数)。如果对齐的负载未折叠,并产生
movdqa
movaps
,则它们仍会在未对齐的地址上出现故障。这甚至适用于128位操作的VEX编码,您可以使用正确的编译选项,而不需要对使用128b内部函数的代码进行源代码更改


对于开始使用内部函数,我建议始终使用未对齐的加载/存储内部函数。(但至少在常见情况下,尽量使数据对齐)。如果您担心未对齐的数据会导致问题,请在性能调整时使用对齐。

旧CPU对您来说是什么?在英特尔Nehalem系列(第一代Core i7s)之前,您一般都是对的,但有几种情况下,即使在旧CPU上,未对齐的加载/存储也是最好的方式。@PaulR-要详细说明吗?毕竟,这是一个问题,但你的回答根本没有考虑到这一点。好吧-我之前有点时间限制,但一个例子是SSSE3之前CPU上的邻里操作-在MNI之前没有有用的双向量水平移位指令,因此未对齐的负载通常是一个必要的缺点。还有一些情况下,未对齐的访问更方便,如果有足够多的其他操作正在进行,则延迟可以隐藏。其他示例:支持外部API(例如,如果您想编写SIMD优化的memcpy或其他标准/传统函数)。@PaulR:很好的解释,但我要补充的是,对于大型缓冲区,您通常希望对大多数数据执行对齐操作。要么进行较小的移动,直到到达对齐边界,然后对其余部分进行对齐。或者对于类似于
memcpy
的操作,执行一个16B未对齐副本,然后执行
p&=~15UL
以对齐指针(向下舍入),然后执行一个16B对齐副本的循环。(其中第一个可能与未对齐的前16B重叠。)这样可以在没有分支和代码膨胀的情况下提供一致的性能。(无论哪种方式,最后一个高达15B的代码都需要一个清理循环。)相反,非HPC代码可以而且确实从SIMD中受益匪浅。看看整数压缩编码或DNA处理。很抱歉,如果您认为SIMD(仅)适用于HPC,那您就大错特错了(SIMD指令集从第一天起就被放在消费者和企业CPU上,并向消费者和企业销售是有原因的)。如果您认为HPC工作负载只使用对齐的负载,那么您也错了(尽管大多数情况下是这样的)-整个HPC空间不是LINPACK and friends。“如果数据事实上是对齐的,未对齐的负载/存储将与对齐的存储具有相同的性能”——我认为仅适用于Sandy Bridge和更高版本?(不知道AMD在这方面的故事。)@PaulR:实际上是Nehalem和后来的,根据上页的说明表。AMD推土机或更高版本上也有同样的故事。(即使K10对加载也没有任何惩罚,但未对齐的存储到对齐的地址速度较慢)。因此,在这一点上,对对齐的数据使用未对齐的加载/存储而受到性能惩罚的硬件完全过时了。在这一点上使用对齐加载内部函数的主要原因是允许将加载折叠到内存操作数中,因为AVX还没有接近于普遍可用。(连西尔弗蒙特都没有。)谢谢你的澄清——我没有