我有一些代码使用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]
我不确定执行此操作的最佳方法是什么,此外,我不确定执行此操作的最佳方法。我正在寻找Broadwell / Haswell上模拟“丢失” _mm_permutevar_epi32(__m128i a, __m128i idx)
的最有效方法。如果可能的话,我宁愿使用128位指令,也不愿使用256位指令(即,我不想加宽128位输入然后缩小结果)。
答案 0 :(得分:3)
尽管彼得·科德斯(Peter Cordes)说得很对,如果您使用的是SSE4.1变体桑迪·布里奇(Sandy Bridge)之前的机器,则AVX指令vpermilps
及其内在_mm_permutevar_ps()
可能会起作用使用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;
}
#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;
}
这可以编译为清晰
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
如果可以保证控制索引始终是32位整数0、1、2或3,则AND掩码是可选的。
答案 1 :(得分:3)
除非在运行新代码,否则在运行时生成立即数是没有用的。立即数是一个字节,它实际上是机器代码指令编码的一部分。如果您具有编译时常数的随机播放(内联+模板扩展之后),那会很好,否则,请忘记那些将控制操作数作为整数 1 的随机播放。
在AVX之前,仅 变量控制随机播放是SSSE3 pshufb
。 (_mm_shuffle_epi8
)。这仍然是AVX2和我认为AVX512中唯一的128位(或行内)整数随机播放指令。
AVX1添加了一些行内32位变量改组,例如vpermilps
(_mm_permutevar_ps
)。 AVX2添加了穿越车道的整数和FP混洗,但奇怪的是,没有vpermd
的128位版本。也许是因为英特尔微体系结构没有对整数数据使用FP随机排序的任何惩罚。 (在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 随机播放(非混合)对于在Sandybridge系列CPU中用于整数数据没有任何惩罚。
如果要对AMD Bulldozer或Ryzen处以罚款,那它比为(v)pshufb
计算随机控制向量的成本要小,而且绝对便宜。
在AMD上,使用vpermd ymm
并忽略输入和输出的前128位(即通过使用强制转换内在函数)会很慢(因为必须拆分其128位SIMD设计)跨越256位的通道越过了几微秒),并且在Intel上也变得更糟,它使其延迟为3c,而不是1个周期。
@Iwill的答案显示了一种从4x32位dword索引的向量计算pshufb
的字节索引的混洗控制向量的方法。但是它使用的SSE4.1 pmulld
在大多数CPU上是2 oups,并且比洗牌容易造成更严重的瓶颈。 (请参见该答案下方的注释中的讨论。)尤其是在没有AVX的较旧CPU上,其中某些时钟每时钟可以执行2 pshufb
,这与现代Intel不同(Haswell和更高版本只有1个shuffle端口,并且容易在shuffle上造成瓶颈。IceLake将根据英特尔的Sunny Cove演示,添加另一个shuffle端口。)
如果您确实必须编写SSSE3或SSE4.1版本,最好还是仅使用SSSE3并使用pshufb
加上左移以在dword中复制一个字节,然后对{ {1}}放入低位,而不是0,1,2,3
。 SSE4.1 pmulld
的运算能力很强,甚至在某些pmulld
速度较慢的CPU上比pshufb
更糟糕。 (在只有SSSE3而不是SSE4.1的CPU(即第一代Core2)上,您可能根本无法从向量化中受益,因为它的运行速度pshufb
很慢。)
在第二代Core2和Goldmont上,pshufb
是单循环指令,具有1个周期的延迟。在Silvermont和第一代Core 2上,它并不是很好。但是总的来说,如果AVX不可用,我建议pshufb
+ pshufb
+ pslld
为另一个por
计算控制向量。
为随机播放做准备的额外随机播放要比在任何支持AVX的CPU上仅使用pshufb
更为糟糕。
脚注1 :
您必须使用vpermilps
或其他东西来选择具有正确的编译时常数的代码路径,这太可怕了;如果您甚至没有SSSE3,请考虑一下。除非跳转表分支能完美预测,否则它可能比标量更糟糕。