【问题标题】:SIMD intrinsics: aligned operation different than unaligned?SIMD 内在函数:对齐操作与未对齐操作不同?
【发布时间】:2015-11-11 00:33:43
【问题描述】:

我开始学习一点关于 SIMD 内在函数的知识。我注意到对于某些函数,有对齐和未对齐的版本,例如_mm_store_si128_mm_storeu_si128。我的问题是,这些函数的执行方式是否不同,如果不是,为什么会有两个不同的版本?

【问题讨论】:

标签: c++ x86 simd intrinsics


【解决方案1】:

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

我想说这同样适用于标量,在任何情况下正确对齐都是正确的,并且在实现最佳性能时会为您节省一些麻烦...

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

【讨论】:

  • 总的来说你是对的,但在某些情况下,未对齐的加载/存储是最好的方法,即使在较旧的 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 和朋友。
【解决方案2】:

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

  • 未对齐的操作:未对齐的数据会导致较小的性能损失,但您的程序仍然有效。

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

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

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

使用 AVX 时,这两种负载都可以折叠到其他操作中。 (AVX 默认行为是允许未对齐的内存操作数)。如果对齐的负载没有折叠,并产生movdqamovaps,那么它们仍然会在未对齐的地址上出错。这甚至适用于 128 位操作的 VEX 编码,您可以通过正确的编译选项获得,而无需使用 128b 内部函数对代码进行源代码更改。

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

【讨论】:

  • “如果数据实际上是对齐的,则未对齐的加载/存储将与对齐的存储具有相同的性能”——我认为仅适用于 Sandy Bridge 及更高版本? (不确定 AMD 的故事。)
  • @PaulR:根据agner.org/optimize 上的说明表,实际上是 Nehalem 和更高版本。 AMD Bulldozer 或更高版本的故事相同。 (即使 K10 对加载也没有惩罚,但未对齐的存储到对齐的地址会更慢)。因此,在这一点上,在对齐数据上使用未对齐加载/存储而具有性能损失的硬件已经完全过时了。此时使用对齐的加载内在函数的主要原因是允许将加载折叠到内存操作数中,因为 AVX 还没有接近普遍可用。 (甚至 Silvermont 也没有。)
  • 感谢您的澄清 - 我不是 100% 确定。
【解决方案3】:

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

【讨论】:

  • 什么是旧 CPU?
  • 英特尔 Nehalem 系列(第一代酷睿 i7s)之前的任何产品。
猜你喜欢
  • 2010-10-30
  • 2016-09-22
  • 2018-10-12
  • 1970-01-01
  • 2014-02-17
  • 2013-02-12
  • 2021-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多