SSE:随机(permutevar)4x32整数

时间:2019-05-08 03:58:59

标签: sse simd intrinsics avx

我有一些代码使用AVX2内部_mm256_permutevar8x32_epi32vpermd来通过索引向量从输入向量中选择整数。现在,我需要同样的东西,但是是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位输入然后缩小结果)。

2 个答案:

答案 0 :(得分:3)

尽管彼得·科德斯(Peter Cordes)说得很对,如果您使用的是SSE4.1变体桑迪·布里奇(Sandy Bridge)之前的机器,则AVX指令vpermilps及其内在_mm_permutevar_ps()可能会起作用使用pshufb的效果也很好。

AVX变体

@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;
}

SSE4.1变体

#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,请考虑一下。除非跳转表分支能完美预测,否则它可能比标量更糟糕。