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

scx*_*scx 4 sse simd intrinsics sse2

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

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

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

Pet*_*des 6

它可以在延迟和吞吐量之间进行权衡,因为在执行向量加载时,单独存储到数组中会导致存储转发停顿.所以这是高延迟,但吞吐量仍然可以,并且它不会与矢量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对元素在内存中是连续的这一事实.在这种情况下,使用(内在函数)movsdmovhps加载两个64位块.(不是movlps:它合并到现有寄存器而不是将高元素归零,因此它对旧内容具有错误依赖性,而movsd将高半部分归零.)或者shufps如果在64位块之间或之内需要重新排序.

"常规模式"是编译器使用通常是movss/ insertps如果与SSE4编译从内存中,或movss负载和unpcklps慢腾腾地对组合,然后另一个unpcklps,unpcklpdmovlhps洗牌成一个寄存器.或者shufps,shufpd如果编译器喜欢在立即的shuffle-control操作数上浪费代码端,而不是智能地使用固定的shuffle.

另请参阅Agner Fog的优化指南,了解一些简单的数据移动指令表,以便更好地了解编译器必须使用的内容以及内容的执行方式.请注意,Haswell和之后每个时钟只能进行1次shuffle.还有x86标签wiki其他链接.


编译器或人类没有真正便宜的方法来执行此操作,在一般情况下,当您有4个单独的内存中不连续的标量时.或者对于寄存器输入,它首先无法优化它们在寄存器中生成的方式,以使它们中的一些已经打包在一起.(例如,对于在寄存器中传递给不能/不能内联的函数的函数args.)

无论如何,除非你在内循环中有这个,否则不是什么大问题.在这种情况下,一定要担心它(并检查编译器的asm输出,看看它是否弄得一团糟,或者如果你自己使用映射到单个指令(如_mm_load_ss/ _mm_shuffle_ps)的内在函数编程聚集,可以做得更好.

如果可能,重新排列数据布局,使数据至少在小块/条带中连续.(参见https://stackoverflow.com/tags/sse/info,特别是这些幻灯片.但有时程序的一部分需要单向数据,另一部分需要另一部分.选择适合需要的情况的布局更快,或更频繁地运行,或者其他什么,并且将其吸收并尽可能地为程序的其他部分做好准备.:P可能转置/转换一次以设置多个SIMD操作,但额外的数据传递没有计算只会耗费时间并且可能会损害您的计算强度(每次将数据加载到寄存器时,您执行的ALU工作量)超出了他们的帮助.


而BTW,实际的收集指令(如AVX2 vgatherdps)并不是很快; 即使在Skylake上,也许不值得在已知位置使用四个32位元素的收集指令.在Broadwell/Haswell,聚会绝对不值得使用.