如果我的理解正确,
_mm_movehdup_ps(a)
得到与
相同的结果 _mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 3, 3))
?
两者在性能上有区别吗?
答案 0 :(得分:4)
_MM_SHUFFLE
首先获得高元素,所以_MM_SHUFFLE(3,3, 1,1)
将进行movshdup
随机播放。
主要区别在于组装级别; movshdup
是复制和改组,如果以后仍然需要输入movaps
(例如,作为水平和的一部分,请避免使用a
复制输入),请参见{{3} },以了解如何在没有movaps
的情况下与使用shufps
的SSE1版本进行编译。
movshdup
/ movsldup
也可以是带有内存源操作数的load + shuffle。 (shufps
显然不能,因为它需要两次相同的输入。)在现代Intel CPU(Sandybridge系列)上, movshdup xmm0, [rdi]
解码为纯负载uop,而不是微融合ALU uop 。因此,它无法与其他洗牌竞争ALU洗牌吞吐量(端口5)。加载端口包含用于执行广播加载(包括movddup
64位广播)和元素对movs[lh]dup
复制的逻辑。像vpermilps xmm0, [rdi], 0x12
或pshufd xmm, [rdi], 0x12
这样更复杂的load + shuffle仍然会解码为多个uops,可能会微弱地融合到load + ALU中,具体取决于uarch。
两个指令的长度相同:movshdup
避免了立即数字节,但是shufps
是SSE1指令,因此它只有2字节操作码,比SSE2和SSE3指令短1个字节。 但是启用了AVX后,vmovshdup
确实节省了一个字节,因为操作码大小的优势已消失。
在只有64位洗牌单元的较旧CPU(如奔腾M和第一代Core 2(Merom))上,性能优势更大。 movshdup
仅在向量的64位一半内随机播放。在Core 2 Merom上,movshdup xmm, xmm
解码为1 uop,但是shufps xmm, xmm, i
解码为3 uop。 (有关说明表和微体系结构指南,请参见Fastest way to do horizontal float vector sum on x86)。另请参阅我的水平总和答案(前面已链接),以获取有关诸如Merom和K8之类的SlowShuffle CPU的更多信息。
如果启用了SSE3,则如果您的编译器未将_mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 1, 1))
优化为_mm_movehdup_ps(a)
所用的同一程序集,则会错过优化。
但是,某些编译器(例如MSVC)通常不会优化内部函数,因此,程序员应了解通过使用内在的复制和混洗指令来避免movaps
指令(例如{ {1}}和pshufd
),而不是洗牌(必然会破坏其目标寄存器)(如movshdup
和shufps
字节移位。)
MSVC也不允许您启用编译器对SSE3的使用,如果对它们使用内在函数,则仅会获得超出基线SSE2(或没有SIMD)的指令。或者,如果启用了AVX,则允许编译器也使用SSE4.2及更早版本,但仍选择不进行优化。如此一来,由人工程序员来寻找优化。 ICC与此类似。如果您确切地知道自己在做什么,并且正在检查编译器的asm输出,则有时这可能是一件好事,因为有时gcc或clang的优化可以使您的代码简陋。
用clang编译并查看它是否使用与源代码中的内在函数相同的指令可能是个好主意;在支持Intel内在函数的4个主要编译器中,它是迄今为止最好的改组优化器,基本上以与编译器通常优化纯C的方式相同的方式(即仅遵循as-if规则来产生相同的结果)来优化内在代码。 / p>
最简单的例子:
psrldq
GCC和带有#include <immintrin.h>
__m128 shuf1(__m128 a) {
return _mm_shuffle_ps(a,a, _MM_SHUFFLE(3,3, 1,1));
}
的clang都可以发现优化:
-O3 -march=core2
ICC shuf1:
movshdup xmm0, xmm0
ret
和MSVC -O3 -march=haswell
(启用引导程序调用约定,而不是通过引用传递SIMD向量)。
-O2 -arch:AVX -Gv