使用 SSE 的任意位置 2 输入混洗
Arbitrary position 2-input shuffling using SSE
我有两个 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)一起完成。
只有 SSE2 我认为你需要 2 次随机播放:unpcklps
交错,然后 unpcklpd same,same
或 shufps same,same
广播低 64 位。
使用 AVX512F,vpermt2ps
可以一次性完成此操作(使用控制向量);我认为 AVX2 或更早版本中没有任何 2 源混洗具有足够精细的粒度和灵活的源位置。并且没有重复元素和交错的固定随机播放。
2 源随机播放在 AVX512 之前很少见:大多数固定随机播放,如 unpckl/h*
和 palignr
。在那之前,它主要只是 [v]shufps
/ [v]shufpd
。 Variable-control 洗牌也很少见:在 AVX 之前,只有 pshufb
。 AVX1/2 添加了一些 variable-control dword-element 随机播放,但仅限于 1 个来源。在 AVX512 之前没有 variable-control 2 源随机播放。
立即洗牌需要超过 4 组 2 位索引来处理对两个 4 元素向量的串联的任意索引。但是 x86 SIMD 指令总是最多有一个 8 位立即操作数。 不幸的是,没有 broadcast-immediate 像 ARM 那样可以有效地创建 1.0f 或其他向量。
AVX
由于每个向量只需要 1 个元素,您可以使用 AVX broadcast-load 然后 vblendps
而不是加载整个向量
Broadcast-loads 与 Intel CPU 上的正常负载的成本相同(不要为 shuffle 端口花费 uop,纯粹在加载端口处理)。在 AVX512F 之前,它们无法折叠到 ALU 指令的内存操作数中,但它们确实避免了 shuffle-port 瓶颈。 AMD CPU 可能仍然需要一个 ALU uop,但它们有更多的 shuffle ALU,因此 shuffle 吞吐量几乎不是瓶颈。 (https://agner.org/optimize/)
不幸的是,Ryzen vbroadcastss xmm, [mem]
是 front-end 的 2 个独立微指令,但它仍然具有 2-per-clock 的吞吐量。
blend-immediate 在 dword 和更高版本的元素上非常有效,并且可以 运行 在 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,clang 的 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 次随机播放。
使用 SSE4.1 你可以做 2x movddup
加载从内存广播 64 位(包括你关心的 32 位)然后 blendps
。第一次加载将加载您关心的 float
之后的 32 位,第二次加载将加载您关心的 float
之前 的 32 位 。
要让 C++ 编译器为你发出这个,你必须 pointer-cast 到 double*
加载 __m128d _mm_loaddup_pd (double const* mem_addr)
,然后使用 _mm_castpd_ps
得到__m128
来自 __m128d
.
https://www.felixcloutier.com/x86/movsldup 也可用于设置 unpcklps
。
我有两个 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)一起完成。
只有 SSE2 我认为你需要 2 次随机播放:unpcklps
交错,然后 unpcklpd same,same
或 shufps same,same
广播低 64 位。
使用 AVX512F,vpermt2ps
可以一次性完成此操作(使用控制向量);我认为 AVX2 或更早版本中没有任何 2 源混洗具有足够精细的粒度和灵活的源位置。并且没有重复元素和交错的固定随机播放。
2 源随机播放在 AVX512 之前很少见:大多数固定随机播放,如 unpckl/h*
和 palignr
。在那之前,它主要只是 [v]shufps
/ [v]shufpd
。 Variable-control 洗牌也很少见:在 AVX 之前,只有 pshufb
。 AVX1/2 添加了一些 variable-control dword-element 随机播放,但仅限于 1 个来源。在 AVX512 之前没有 variable-control 2 源随机播放。
立即洗牌需要超过 4 组 2 位索引来处理对两个 4 元素向量的串联的任意索引。但是 x86 SIMD 指令总是最多有一个 8 位立即操作数。 不幸的是,没有 broadcast-immediate 像 ARM 那样可以有效地创建 1.0f 或其他向量。
AVX
由于每个向量只需要 1 个元素,您可以使用 AVX broadcast-load 然后 vblendps
Broadcast-loads 与 Intel CPU 上的正常负载的成本相同(不要为 shuffle 端口花费 uop,纯粹在加载端口处理)。在 AVX512F 之前,它们无法折叠到 ALU 指令的内存操作数中,但它们确实避免了 shuffle-port 瓶颈。 AMD CPU 可能仍然需要一个 ALU uop,但它们有更多的 shuffle ALU,因此 shuffle 吞吐量几乎不是瓶颈。 (https://agner.org/optimize/)
不幸的是,Ryzen vbroadcastss xmm, [mem]
是 front-end 的 2 个独立微指令,但它仍然具有 2-per-clock 的吞吐量。
blend-immediate 在 dword 和更高版本的元素上非常有效,并且可以 运行 在 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,clang 的 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 次随机播放。
使用 SSE4.1 你可以做 2x movddup
加载从内存广播 64 位(包括你关心的 32 位)然后 blendps
。第一次加载将加载您关心的 float
之后的 32 位,第二次加载将加载您关心的 float
之前 的 32 位 。
要让 C++ 编译器为你发出这个,你必须 pointer-cast 到 double*
加载 __m128d _mm_loaddup_pd (double const* mem_addr)
,然后使用 _mm_castpd_ps
得到__m128
来自 __m128d
.
https://www.felixcloutier.com/x86/movsldup 也可用于设置 unpcklps
。