使用未格式化数据时,loadu_ps 和 set_ps 有什么区别?

What is the difference between loadu_ps and set_ps when using unformatted data?

我有一些数据没有存储为数组结构。将数据加载到寄存器的最佳做法是什么?

__m128 _mm_set_ps (float e3, float e2, float e1, float e0) // or __m128 _mm_loadu_ps (float const* mem_addr)

使用 _mm_loadu_ps,我会将数据复制到临时堆栈数组中,而不是直接将数据复制为值。有区别吗?

这可能是延迟和吞吐量之间的权衡,因为在执行矢量加载时将单独存储到数组中会导致 a store-forwarding stall。所以它是高延迟,但吞吐量仍然可以,并且它不会与矢量洗牌执行单元的周围代码竞争。因此,如果周围的代码也有混洗操作,那么它可能是吞吐量的胜利,而不是 3 次混洗以在第一个元素的标量加载之后将 3 个元素插入 XMM 寄存器。无论哪种方式,它仍然有很多总微指令,这是另一个吞吐量瓶颈。

大多数编译器(如 gcc 和 clang)在使用 -O3 进行优化时都可以很好地使用 _mm_set_ps (),无论输入是在内存还是寄存器中。我会推荐它,除非在某些特殊情况下.

最常见的 missed-optimization 和 _mm_set 是当输入 之间存在一些局部性时。例如不要做 _mm_set_ps(a[i+2], a[i+3], a[i+0], a[i+1]]),因为许多编译器会使用它们的常规模式,而不会利用 2 对元素在内存中是连续的这一事实。在这种情况下,使用 movsdmovhps(的内在函数)加载两个 64 位块。 (不是 movlps:它合并到现有的寄存器中而不是将高位元素置零,因此它对旧内容有错误的依赖性,而 movsd 将高位元素置零。)或者 shufps如果 64 位块之间或内部需要一些重新排序。

如果使用 SSE4 编译,编译器使用的 "regular pattern" 通常是 movss / insertps,或者 movss 加载和 unpcklps 混洗到组合对,然后组合另一个 unpcklpsunpcklpdmovlhps 以洗牌到一个寄存器中。或者 shufpsshufpd 如果编译器喜欢在立即 shuffle-control 操作数上浪费 code-side 而不是智能地使用固定洗牌。

另见 Agner Fog's optimization guides for some handy tables of data-movement instructions to get a better idea of what the compiler has to work with, and how stuff performs. Note that Haswell and later can only do 1 shuffle per clock. Also other links in the x86 tag wiki


在一般情况下,当您有 4 个在内存中根本不连续的独立标量时,编译器或人类并没有真正便宜的方法。或者对于寄存器输入,它无法优化它们首先在寄存器中生成的方式,以将其中一些已经打包在一起。 (例如,对于在寄存器中传递给不能/不内联的函数的函数参数。)

无论如何,这没什么大不了的,除非你把它放在一个内部循环中。在那种情况下,肯定会担心它(并且 看看它是否弄得一团糟,或者如果您使用映射到单个指令的内在函数对自己进行编程,则可以做得更好_mm_load_ss / _mm_shuffle_ps).

如果可能,请重新安排您的数据布局,使数据至少在小块/条带中连续。 (参见 https://whosebug.com/tags/sse/info, specifically these slides。但有时程序的一部分需要一种方式的数据,而另一部分需要另一种方式。选择适合需要更快、运行更频繁等情况的布局,并吸收它并为程序的其他部分尽力而为。:P 可能转置/转换一次以设置多个 SIMD 操作,但额外传递数据而不进行计算只会浪费时间并且可能会损害您的计算强度(每次将数据加载到寄存器时,您所做的 ALU 工作量)比它们的帮助要多。


顺便说一句,实际的收集指令(如 AVX2 vgatherdps)不是很快;即使在 Skylake 上,也可能不值得在已知位置对四个 32 位元素使用收集指令。在 Broadwell / Haswell 上,gather 绝对不值得为此使用。