使用英特尔AVX通过面罩进行随机播放

时间:2018-04-30 10:45:16

标签: c++ sse simd intrinsics avx

我是AVX编程的新手。我有一个需要被驱动的寄存器,准确地说我想将256位寄存器R1中的几个字节混合到空寄存器R2中。我想定义一个掩码,它告诉shuffle操作应该将旧寄存器R1中的哪个字节复制到新寄存器中的哪个位置。

掩码应该如下所示(Src:R1中的字节位置,目标:R2中的字节位置):

{(0,0),(1,1),(1,4),(2,5),...}

这意味着要复制几个字节两次。

我不能100%确定我应该使用哪种功能。我尝试了这两个AVX功能,第二个只使用2个通道。

__m256 _mm256_permute_ps (__m256 a, int imm8)
__m256 _mm256_shuffle_ps (__m256 a, __m256 b, const int imm8)

我对imm8中的Shuffle Mask完全感到困惑,以及如何设计它将如上所述工作。

我看过这个slides(第26页)是_MM_SHUFFLE被描述但我找不到解决方案来解决我的问题。

有没有教程如何设计这样的面具?或者这两个方法的示例函数能够深入理解它们吗?

提前感谢提示

1 个答案:

答案 0 :(得分:5)

TL:DR:您可能需要多次随机播放来处理交叉路口,或者如果您的模式继续完全相同,则可以使用_mm256_cvtepu16_epi32vpmovzxwd)然后_mm256_blend_epi16

对于x86 shuffle(我认为与大多数SIMD指令集一样),目标位置是隐式的。一个shuffle-control常量只有目标顺序的源索引,是否是imm8被编译+组装成asm指令,或者它是否是每个元素中带索引的向量。

每个目标位置只读取一个源位置,但可以多次读取相同的源位置。每个目标元素都从shuffle源获取一个值。

有关dst = _mm_shuffle_epi32(src, _MM_SHUFFLE(d,c,b,a))的普通C版本,请参阅Convert _mm_shuffle_epi32 to C expression for the permutation?,其中显示了如何使用控制字节。

(对于pshufb / _mm_shuffle_epi8,高位设置的元素为目标位置而不是读取任何源元素,但其他x86混洗忽略了随机控制向量中的所有高位。 )

没有AVX512合并屏蔽,也没有混合到目的地的随机播放。有一些双源shuffle,如_mm256_shuffle_psvshufps),它可以将来自两个源的元素混合在一起,以产生单个结果向量。 如果您想保留一些目标元素不成文,您可能需要随机播放然后混合,例如使用_mm256_blendv_epi8,或者如果您可以使用16位粒度的混合,则可以使用更高效的即时混合_mm256_blend_epi16,甚至更好_mm256_blend_epi32(AVX2 vpblendd便宜在英特尔CPU上为_mm256_and_si256,如果您需要完全混合,则是最佳选择,如果它可以完成工作;请参阅http://agner.org/optimize/

对于您的问题(在Cannonlake中没有AVX512VBMI vpermb),您无法将单个字节从低16“通道”移动到__m256i向量的高16“通道”只需一次操作。

AVX shuffle不像一个完整的256位SIMD,它们更像是两个并行的128位操作。唯一的例外是一些具有32位粒度或更大粒度的AVX2车道交叉混洗,例如vpermd_mm256_permutevar8x32_epi32)。以及pmovzx / pmovsx的AVX2版本,例如pmovzxbq将XMM寄存器的低4字节零延伸到YMM寄存器的4个字节,而不是YMM寄存器的每半个字节的低2字节。这使得它对内存源操作数更有用。

但无论如何,pshufb_mm256_shuffle_epi8)的AVX2版本在256位向量的两个通道中进行两次单独的16x16字节混洗。

你可能会想要这样的东西

// Intrinsics have different types for integer, float and double vectors
// the asm uses the same registers either way
__m256i  shuffle_and_blend(__m256i dst, __m256i src)
{
    // setr takes element in low to high order, like a C array init
    // unlike the standard Intel notation where high element is first
    const __m256i  shuffle_control = _mm256_setr_epi8(
          0,      1,  -1, -1,   1,      2, ...);
    // {(0,0),  (1,1), (zero)  (1,4), (2,5),...}  in your src,dst notation
    // Use -1 or 0x80 or anything with the high bit set
    //  for positions you want to leave unmodified in dst
   // blendv uses the high bit as a blend control, so the same vector can do double duty

    // maybe need some lane-crossing stuff depending on the pattern of your shuffle.
    __m256i  shuffled = _mm256_shuffle_epi8(src, shuffle_control);

    // or if the pattern continues, and you're just leaving 2 bytes between every 2-byte group:
    shuffled = _mm256_cvtepu16_epi32(src);  // if src is a __m128i

    __m256i  blended = _mm256_blendv_epi8(shuffled, dst, shuffle_control);
    // blend dst elements we want to keep into the shuffled src result.
    return blended;
}    

请注意,pshufb编号从第2个16字节的0开始重新开始。 __m256i的两半可能不同,但它们无法读取另一半的元素。如果你需要高车道的位置来从低车道获得字节,你需要更多的改组+混合(例如包括vinserti128vperm2i128,或者可能是vpermd车道交叉dword shuffle)以某些顺序将所需的所有字节放入一个16字节组中。

(实际上_mm256_shuffle_epi8(PSHUFB)忽略了一个shuffle索引中的位4..6,因此写171相同,但是非常具有误导性。它正在有效地执行{ {1}},只要没有设置高位。如果在随机控制向量中设置了高位,则将该元素归零。我们在这里不需要该功能; %16没有关心它正在替换的元素的旧值

无论如何,这个简单的2指令示例仅在模式不继续时才有效。如果你想帮助设计真正的洗牌,你将不得不提出一个更具体的问题。

顺便说一下,我注意到你的混合模式使用了2个新字节然后2个跳过2 。如果继续,则可以使用_mm256_blendv_epi8 _mm256_blend_epi16而不是vpblendw,因为该指令在Intel CPU上仅运行1 uop而不是2。它还允许您使用AVX512BW blendv,这是当前Skylake-AVX512 CPU中可用的16位shuffle,而不是可能更慢的AVX512VBMI vpermw

实际上,它可能会让你使用vpermbvpmovzxwd)将16位元素零扩展为32位,作为一个交叉的shuffle。然后与_mm256_cvtepu16_epi32混合。