使用SSE的任意位置2输入改组

时间:2019-07-16 11:01:07

标签: c++ sse simd avx

我有两个4分量矢量,我将它们加载到两个__m128变量中。 然后,我需要将它们洗牌,以便结果看起来像这样:

给出:

__m128 mmMin = _mm_load_ps(&glm::vec4(-1.0f,-2.0f,-3.0f,-4.0f)[0]);
__m128 mmMax = _mm_load_ps(&glm::vec4(1.0f,2.0f,3.0f,4.0f)[0]);

我希望改组的结果看起来像这样:

 //    {mmMin.x,mmMax.x,mmMin.x,mmMax.x}

但是我发现无法使用_mm_shuffle_ps

SSE docs中我总是看到_mm_shuffle_ps口罩 首先从__m128的低2个分量中插入2个值,然后从高2个分量中插入2个。

SPU内在函数具有si_shufb方法,该方法允许定义基于qword的蒙版并随机播放我希望的任何位置。 SSE中有类似的方法吗?

我正在使用SSE2 ,但也很高兴了解如何使用其他版本(包括AVX)来完成此操作。

1 个答案:

答案 0 :(得分:5)

只有SSE2,我认为您需要进行2次混洗:unpcklps进行交织,然后unpcklpd same,sameshufps same,same进行低64位广播。

借助AVX512F,vpermt2ps可以一次随机播放(使用控制向量);我认为在AVX2或更早的版本中没有任何2源改组,且粒度足够细且源位置灵活。而且没有固定的随机播放可以重复元素和交织。

在AVX512之前,很少有

2-source改组:unpckl/h*palignr之类的固定改组。在此之前,基本上只使用[v]shufps / [v]shufpd。可变控制洗牌也很少见:在AVX之前,唯一的一种是pshufb。 AVX1 / 2添加了一些可变控制的双字元素改组,但仅适用于1个源。在AVX512之前,没有可变控制2源随机播放。

立即洗牌将需要超过4组2位索引来处理将两个4元素向量串联在一起的任意索引。但是 x86 SIMD指令始终最多具有8位立即数。不幸的是,没有像ARM这样的广播即时介质可以有效地创建1.0f或任何矢量。


AVX

由于每个向量仅需要1个元素,因此无需加载整个向量,可以使用AVX广播加载,然后使用vblendps

广播负载的费用与Intel CPU上的正常负载相同(不要为shuffle端口支付uop费用,完全在负载端口中处理)。在AVX512F之前,它们无法折叠到ALU指令的内存操作数中,但是它们确实避免了随机端口瓶颈。 AMD CPU可能仍需要ALU uop,但是它们具有更多的改组ALU,因此改组吞吐量几乎不是瓶颈。 (https://agner.org/optimize/

不幸的是,Ryzen vbroadcastss xmm, [mem]是2个独立的前端,但仍具有每时钟2个吞吐量。

dword及更高版本元素上的

blend-immediate非常高效,可以在Haswell及更高版本上的任何端口上运行,或在SnB / IvB和Ryzen上的2个端口上运行。但是即使在Nehalem上,仍然具有单uop / 1c延迟。

#include <immintrin.h>
__m128 broadcast_interleave_scalars_avx(const float *min, const float *max) {
    __m128 minx = _mm_broadcast_ss(min);
    __m128 maxx = _mm_broadcast_ss(max);
    return _mm_blend_ps(minx, maxx, 0b1010);
}

On Godbolt,叮当声的asm注释确认我正确掌握了混合常量:

    vbroadcastss    xmm0, dword ptr [rdi]
    vbroadcastss    xmm1, dword ptr [rsi]
    vblendps        xmm0, xmm0, xmm1, 10 # xmm0 = xmm0[0],xmm1[1],xmm0[2],xmm1[3]

如果您的数据已经在寄存器中,而不是新加载的,则可能只需要使用2个shuffle。


使用SSE4.1 ,您可能能够执行2次movddup加载以从内存中广播64位(包括您关心的32位),然后广播blendps。第一次加载将在您关心的float之后加载32位,第二次加载将在您关注的float之前 加载32位。

要使C ++编译器为您提供此功能,您必须将指针投射到__m128d _mm_loaddup_pd (double const* mem_addr)加载的double*,然后使用_mm_castpd_ps获取{{1} }来自__m128

https://www.felixcloutier.com/x86/movsldup对于设置__m128d也可能很有用。