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

Tho*_*gas 7 c++ sse simd intrinsics avx

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

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

{(0,0),(1,1),(1,4),(2,5),...}
Run Code Online (Sandbox Code Playgroud)

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

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

__m256 _mm256_permute_ps (__m256 a, int imm8)
__m256 _mm256_shuffle_ps (__m256 a, __m256 b, const int imm8)
Run Code Online (Sandbox Code Playgroud)

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

我看过这张幻灯片(第26页)是_MM_SHUFFLE被描述但是我没有找到解决方案来解决我的问题.

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

提前感谢提示

Pet*_*des 7

TL:DR:您可能需要多次shuffle来处理交叉路口,或者如果您的模式继续完全相同,您可以使用_mm256_cvtepu16_epi32(vpmovzxwd)然后_mm256_blend_epi16.


对于x86 shuffle(我认为像大多数SIMD指令集一样),目标位置是隐式的.一个shuffle-control常量只有目标顺序的源索引,无论imm8是编译+汇编到asm指令还是它是一个在每个元素中都有索引的向量.

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

请参阅将_mm_shuffle_epi32转换为C表达式以进行置换?对于普通C版本dst = _mm_shuffle_epi32(src, _MM_SHUFFLE(d,c,b,a)),显示如何使用控制字节.

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

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


对于您的问题(vpermb在Cannonlake中没有AVX512VBMI ),您无法通过单个操作将单个字节从低16"通道"移动到__m256i向量的高16"通道" .

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

但无论如何,AVX2版本的pshufb(_mm256_shuffle_epi8)在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;
}    
Run Code Online (Sandbox Code Playgroud)

请注意,pshufb编号从第2个16字节的0开始重新开始.两半__m256i可以是不同的,但是它们不能从另一半读取元素.如果你需要在高通道中的位置来从低通道获取字节,你需要更多的混洗+混合(例如包括vinserti128或者vperm2i128,或者可能是一个vpermd交叉的双字拖曳)来将你需要的所有字节变成一个16字节以某种顺序组.

(实际上_mm256_shuffle_epi8(PSHUFB)忽略了一个shuffle索引中的位4..6,所以写入17是相同的1,但是非常误导.%16只要没有设置高位,它就会有效地执行a .如果高位设置为shuffle-control向量,它将该元素归零.我们在这里不需要那个功能; _mm256_blendv_epi8不关心它正在替换的元素的旧值)

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


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

或者实际上,它可能会让你使用vpmovzxwd(_mm256_cvtepu16_epi32)将16位元素零扩展到32位,作为一个交叉的shuffle.然后与之混合dst.