Joh*_*nck 4 sse simd intrinsics avx
我有一些代码使用 AVX2 内在_mm256_permutevar8x32_epi32又名vpermd通过索引向量从输入向量中选择整数。现在我需要同样的东西,但需要 4x32 而不是 8x32。 _mm_permutevar_ps是为浮点数做的,但我使用的是整数。
一个想法是_mm_shuffle_epi32,但我首先需要将我的 4x32 索引值转换为单个整数,即:
imm[1:0] := idx[31:0]
imm[3:2] := idx[63:32]
imm[5:4] := idx[95:64]
imm[7:6] := idx[127:96]
Run Code Online (Sandbox Code Playgroud)
我不确定什么是最好的方法,而且我不确定这是最好的方法。我正在寻找 Broadwell/Haswell 上最有效的方法来模拟 "missing" _mm_permutevar_epi32(__m128i a, __m128i idx)。如果可能的话,我宁愿使用 128 位指令而不是 256 位指令(即我不想扩大 128 位输入然后缩小结果)。
尽管 Peter Cordes 说 AVX 指令vpermilps及其内在指令_mm_permutevar_ps()可能会完成这项工作是正确的,但如果您在比 Sandy Bridge 更早的机器上工作,那么使用 SSE4.1 变体pshufb也能很好地工作。
归功于@PeterCordes
#include <stdio.h>
#include <immintrin.h>
__m128i vperm(__m128i a, __m128i idx){
return _mm_castps_si128(_mm_permutevar_ps(_mm_castsi128_ps(a), idx));
}
int main(int argc, char* argv[]){
__m128i a = _mm_set_epi32(0xDEAD, 0xBEEF, 0xCAFE, 0x0000);
__m128i idx = _mm_set_epi32(1,0,3,2);
__m128i shu = vperm(a, idx);
printf("%04x %04x %04x %04x\n", ((unsigned*)(&shu))[3],
((unsigned*)(&shu))[2],
((unsigned*)(&shu))[1],
((unsigned*)(&shu))[0]);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
#include <stdio.h>
#include <immintrin.h>
__m128i vperm(__m128i a, __m128i idx){
idx = _mm_and_si128 (idx, _mm_set1_epi32(0x00000003));
idx = _mm_mullo_epi32(idx, _mm_set1_epi32(0x04040404));
idx = _mm_or_si128 (idx, _mm_set1_epi32(0x03020100));
return _mm_shuffle_epi8(a, idx);
}
int main(int argc, char* argv[]){
__m128i a = _mm_set_epi32(0xDEAD, 0xBEEF, 0xCAFE, 0x0000);
__m128i idx = _mm_set_epi32(1,0,3,2);
__m128i shu = vperm(a, idx);
printf("%04x %04x %04x %04x\n", ((unsigned*)(&shu))[3],
((unsigned*)(&shu))[2],
((unsigned*)(&shu))[1],
((unsigned*)(&shu))[0]);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这编译成清脆的
0000000000400550 <vperm>:
400550: c5 f1 db 0d b8 00 00 00 vpand 0xb8(%rip),%xmm1,%xmm1 # 400610 <_IO_stdin_used+0x20>
400558: c4 e2 71 40 0d bf 00 00 00 vpmulld 0xbf(%rip),%xmm1,%xmm1 # 400620 <_IO_stdin_used+0x30>
400561: c5 f1 eb 0d c7 00 00 00 vpor 0xc7(%rip),%xmm1,%xmm1 # 400630 <_IO_stdin_used+0x40>
400569: c4 e2 79 00 c1 vpshufb %xmm1,%xmm0,%xmm0
40056e: c3 retq
Run Code Online (Sandbox Code Playgroud)
如果您可以保证控制索引始终是 32 位整数 0、1、2 或 3,则 AND 掩码是可选的。
在运行时生成立即数是没有用的,除非您正在 JIT 新代码。立即数是一个字节,它实际上是机器代码指令编码的一部分。如果您有一个编译时常量 shuffle(在内联 + 模板扩展之后),那就太好了,否则忘记那些将控制操作数作为整数1 的洗牌。
在 AVX 之前,唯一的可变控制 shuffle 是 SSSE3 pshufb。( _mm_shuffle_epi8). 这仍然是AVX2 中唯一的 128 位(或车道内)整数洗牌指令,我认为是 AVX512。
AVX1 添加了一些车道内 32 位变量洗牌,例如vpermilps( _mm_permutevar_ps)。AVX2 增加了车道交叉整数和 FP shuffle,但有点奇怪的是没有 128 位版本的vpermd. 也许是因为英特尔微架构对整数数据使用 FP shuffle 没有任何惩罚。(这在 Sandybridge 家族中是正确的,我只是不知道这是否是 ISA 设计的部分原因)。但是你会认为如果那是你“应该”做的,他们会添加__m128i内在函数vpermilps。或者也许编译器/内在函数设计人员不同意 asm 指令集人员?
如果您有一个 32 位索引的运行时变量向量,并且想要以 32 位粒度进行随机播放,那么到目前为止,您最好的选择是仅使用 AVX _mm_permutevar_ps。
_mm_castps_si128( _mm_permutevar_ps (_mm_castsi128_ps(a), idx) )
至少在 Intel 上,当在整数指令之间使用时,它甚至不会引入任何额外的旁路延迟paddd;即特别是FP shuffle(不是混合)对 Sandybridge 系列 CPU 中的整数数据使用没有惩罚。
如果对 AMD Bulldozer 或 Ryzen 有任何惩罚,那是很小的,而且绝对比计算 .shuffle 控制向量的成本便宜(v)pshufb。
使用vpermd ymm和(通过使用铸造内在即)忽略输入和输出的上部128个比特将是多对AMD慢(因为它的128位SIMD设计必须分裂车道穿越256位混洗成若干微指令),并且还更坏在英特尔上,它使其延迟为 3c 而不是 1 个周期。
@Iwill 的回答显示了一种pshufb从 4x32 位双字索引向量计算字节索引的随机控制向量的方法。但它使用 SSE4.1 pmulld,它在大多数 CPU 上是 2 uop,并且很容易成为比 shuffle 更糟糕的瓶颈。(请参阅该答案下的评论中的讨论。)特别是在没有 AVX 的旧 CPU 上,其中一些 CPUpshufb每时钟可以执行 2个与现代英特尔不同的时钟(Haswell 和后来只有 1 个 shuffle 端口并且很容易在 shuffle 上遇到瓶颈。IceLake 将添加另一个 shuffle 端口,根据英特尔的 Sunny Cove 演示文稿。)
如果您必须编写此版本的 SSSE3 或 SSE4.1 版本,最好仍然只使用 SSSE3 并使用pshufb加左移来复制双字中的一个字节,然后再将其 ORing0,1,2,3到低位,而不是pmulld. SSE4.1pmulld是多个 uops,甚至比pshufb某些带有慢速pshufb. (在只有 SSSE3 而不是 SSE4.1 的 CPU(即第一代 Core2)上,您可能根本无法从矢量化中受益,因为它具有 slow-ish pshufb。)
在第二代 Core2 和 Goldmont 上,pshufb是具有 1 个周期延迟的单uop指令。在 Silvermont 和第一代 Core 2 上,情况不太好。但总的来说,如果 AVX 不可用,我建议pshufb+ pslld+por为另一个计算控制向量。pshufb
为 shuffle 准备的额外 shuffle 比仅vpermilps在任何支持 AVX 的 CPU 上使用要糟糕得多。
脚注1:
您必须使用 aswitch或其他东西来选择具有正确编译时常量整数的代码路径,这太可怕了;仅在您甚至没有可用的 SSSE3 时才考虑。除非跳转表分支完美预测,否则它可能比标量更糟糕。