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被描述但是我没有找到解决方案来解决我的问题.
有没有教程如何设计这样的面具?或者这两个方法的示例函数能够深入理解它们吗?
提前感谢提示
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.