【发布时间】:2015-11-11 00:33:43
【问题描述】:
我开始学习一点关于 SIMD 内在函数的知识。我注意到对于某些函数,有对齐和未对齐的版本,例如_mm_store_si128 和_mm_storeu_si128。我的问题是,这些函数的执行方式是否不同,如果不是,为什么会有两个不同的版本?
【问题讨论】:
标签: c++ x86 simd intrinsics
我开始学习一点关于 SIMD 内在函数的知识。我注意到对于某些函数,有对齐和未对齐的版本,例如_mm_store_si128 和_mm_storeu_si128。我的问题是,这些函数的执行方式是否不同,如果不是,为什么会有两个不同的版本?
【问题讨论】:
标签: c++ x86 simd intrinsics
我会说“始终对齐(尽可能)”,这样无论如何您都可以得到保障。一些平台不支持非对齐访问,另一些平台的性能会大幅下降。如果您选择对齐访问,无论如何您都将获得最佳性能。在某些平台上可能会有少量内存成本,但这是非常值得的,因为如果您使用 SIMD,则意味着您会追求性能。我想不出为什么要实现未对齐的代码路径。也许如果您必须处理一些旧设计,而这些设计并未考虑到 SIDM,但我认为这种可能性微乎其微。
我想说这同样适用于标量,在任何情况下正确对齐都是正确的,并且在实现最佳性能时会为您节省一些麻烦...
至于为什么未对齐的访问可能会更慢甚至不受支持 - 这是因为硬件的工作方式。假设您有一个 64 位整数和一个 64 位内存控制器,如果您的整数正确对齐,内存控制器可以一次性访问它。但如果它是偏移的,内存控制器将不得不执行 2 次操作,而且 CPU 可能需要移动数据以正确组合它。由于这是次优的,一些平台甚至不隐含地支持它,作为提高效率的手段。
【讨论】:
memcpy 这样的东西,做一个16B 未对齐的副本,然后p &= ~15UL 对齐指针(向下舍入),然后循环16B 对齐的副本。 (其中第一个可能与未对齐的第一个 16B 重叠。)这为您提供了没有分支和代码膨胀的对齐性能。 (无论哪种方式,您仍然需要最后一个高达 15B 的清理循环。)
如果数据实际上是对齐的,则未对齐的加载/存储将与对齐的存储具有相同的性能。
未对齐的操作:未对齐的数据会导致较小的性能损失,但您的程序仍然有效。
对齐的操作:未对齐的数据会导致故障,让您检测意外未对齐的数据,而不是默默地导致性能下降。
现代 CPU 对非对齐负载有很好的支持,但是当负载跨越缓存线边界时,性能仍然会受到很大影响。
使用 SSE 时,对齐的负载可以作为内存操作数折叠到其他操作中。这会略微提高代码大小和吞吐量。
使用 AVX 时,这两种负载都可以折叠到其他操作中。 (AVX 默认行为是允许未对齐的内存操作数)。如果对齐的负载没有折叠,并产生movdqa 或movaps,那么它们仍然会在未对齐的地址上出错。这甚至适用于 128 位操作的 VEX 编码,您可以通过正确的编译选项获得,而无需使用 128b 内部函数对代码进行源代码更改。
为了开始使用内在函数,我建议始终使用未对齐的加载/存储内在函数。 (但至少在常见情况下尽量让您的数据对齐)。如果您担心未对齐的数据会导致问题,请在性能调整时使用对齐。
【讨论】:
在较旧的 CPU 上,对齐和未对齐的加载/存储之间存在显着的性能差异。在较新的 CPU 上,差异要小得多,但作为“经验法则”,您仍应尽可能选择对齐版本。
【讨论】: