我有一些数据没有存储为数组结构。在寄存器中加载数据的最佳做法是什么?
__m128 _mm_set_ps (float e3, float e2, float e1, float e0)
// or
__m128 _mm_loadu_ps (float const* mem_addr)
使用_mm_loadu_ps
,我将数据复制到临时堆栈数组中,而不是直接将数据复制为值。有区别吗?
答案 0 :(得分:5)
它可以在延迟和吞吐量之间进行权衡,因为当您执行向量加载时,单独存储到数组中将导致a store-forwarding stall。所以这是高延迟,但吞吐量仍然可以,并且它不会与矢量shuffle执行单元的周围代码竞争。因此,如果周围的代码也具有shuffle操作,那么它可以是吞吐量获胜,而在第一个的标量加载之后将3个元素插入XMM寄存器则需要3次shuffle。无论哪种方式,它仍然是很多总的uops,这是另一个吞吐量瓶颈。
gcc和clang等大多数编译器在使用_mm_set_ps ()
进行优化时,使用-O3
做得非常好,无论输入是在内存还是寄存器中。我推荐它,除了一些特殊情况。
_mm_set
最常见的遗漏优化是指输入之间存在某些位置。例如不做_mm_set_ps(a[i+2], a[i+3], a[i+0], a[i+1]])
,因为许多编译器将使用它们的常规模式而不利用2对元素在内存中连续的事实。在这种情况下,使用(内在函数)movsd
和movhps
加载两个64位块。 (不是movlps
:它合并到现有寄存器而不是将高元素归零,因此它对旧内容具有错误依赖性,而movsd
将高半部分归零。)或shufps
如果在64位块之间或之内需要重新排序。
如果使用SSE4编译,编译器使用的“常规模式”通常是movss
/ insertps
,或movss
加载和unpcklps
shuffle组合对和然后另一个unpcklps
,unpcklpd
或movlhps
随机播放到一个注册表中。或者shufps
或shufpd
如果编译器喜欢在立即的shuffle-control操作数上浪费代码端而不是智能地使用固定的shuffle。
另请参阅Agner Fog's optimization guides一些方便的数据移动指令表,以便更好地了解编译器必须使用的内容以及内容的执行方式。请注意,Haswell和之后每个时钟只能进行1次shuffle。 the x86 tag wiki中的其他链接。
对于编译器或人类来说,没有非常便宜的方法来执行此操作,在一般情况下,当您有4个单独的内存中不连续的标量时。或者对于寄存器输入,它首先无法优化它们在寄存器中生成的方式,以使它们中的一些已经打包在一起。 (例如,对于在寄存器中传递给不能/不能内联的函数的函数args。)
无论如何,除非你有一个内循环,否则不是什么大不了的事。在这种情况下,肯定会担心(和check the compiler's asm output,看看它是否弄得一团糟,或者如果你自己用内在函数编程聚集,可以做得更好,这些内在函数映射到{{1 } / _mm_load_ss
)。
如果可能,重新排列数据布局,使数据至少在小块/条带中连续。 (参见https://stackoverflow.com/tags/sse/info,特别是these slides。但有时程序的一部分需要单向数据,另一部分则需要另一部分。选择对需要更快的情况有利的布局,或者运行得更频繁,或者其他什么,并且将它吸收并尽可能地为程序的其他部分做准备。:P可能转置/转换一次以设置多个SIMD操作,但额外的数据传输没有计算只是吮吸准备时间并且可能会损害您的计算强度(每次将数据加载到寄存器中时,您执行的ALU工作量)超出了他们的帮助。
而BTW,实际的收集指令(如AVX2 _mm_shuffle_ps
)并不是很快;即使在Skylake上,也许不值得在已知位置使用四个32位元素的收集指令。在Broadwell / Haswell,聚会绝对不值得使用。