我是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被描述但我找不到解决方案来解决我的问题。
有没有教程如何设计这样的面具?或者这两个方法的示例函数能够深入理解它们吗?
提前感谢提示
答案 0 :(得分:5)
TL:DR:您可能需要多次随机播放来处理交叉路口,或者如果您的模式继续完全相同,则可以使用_mm256_cvtepu16_epi32
(vpmovzxwd
)然后_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_ps
(vshufps
),它可以将来自两个源的元素混合在一起,以产生单个结果向量。 如果您想保留一些目标元素不成文,您可能需要随机播放然后混合,例如使用_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
的两半可能不同,但它们无法读取另一半的元素。如果你需要高车道的位置来从低车道获得字节,你需要更多的改组+混合(例如包括vinserti128
或vperm2i128
,或者可能是vpermd
车道交叉dword shuffle)以某些顺序将所需的所有字节放入一个16字节组中。
(实际上_mm256_shuffle_epi8
(PSHUFB)忽略了一个shuffle索引中的位4..6,因此写17
与1
相同,但是非常具有误导性。它正在有效地执行{ {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
。
实际上,它可能会让你使用vpermb
(vpmovzxwd
)将16位元素零扩展为32位,作为一个交叉的shuffle。然后与_mm256_cvtepu16_epi32
混合。