(我假设您立即向后退(17 的选择器应该是低位,而不是高位)并且您的向量实际上是按低元素优先顺序编写的)。
这样的功能如何才能高效实现?
在这种情况下使用 AVX2 vpermd ( _mm256_permutevar8x32_epi32 )。它需要一个控制向量而不是立即数,来为 8 个输出元素保存 8 个选择器。因此,您必须加载一个常量并将其用作控制操作数。
由于您只关心输出向量的下半部分,因此您的向量常数可以只有__m128i,节省空间。 vmovdqa xmm, [mem] 零扩展到相应的 YMM 向量。用内在函数用 C 语言编写它可能不方便,但 _mm256_castsi128_si256 应该可以工作。甚至_mm256_broadcastsi128_si256 因为广播负载同样便宜。尽管如此,一些编译器可能会通过常量传播将其悲观为内存中的实际 32 字节常量。如果你知道汇编,编译器的输出经常令人失望。
如果您想在源代码中获取实际的整数位图,您可以使用 C++ 模板在编译时将其转换为正确的向量常量。 Agner Fog's Vector Class Library(现在是 Apache 许可的,以前是 GPL)有一些类似的东西,根据常量和支持的目标 ISA,使用 C++ 模板将整数常量转换为单个混合或混合指令序列。但它的 shuffle 模板采用索引列表,而不是位图。
但我认为您想问的是为什么/如何设计 x86 洗牌。
Intel有提供这样的功能吗?
是的,在带有 AVX512F 的硬件中(加上 AVX512VL 以在 256 位向量上使用它)。
您正在寻找 vpcompressd,它是 BMI2 pext 的向量元素等价物。 (但它将控制操作数作为掩码寄存器值,而不是立即数。)内在是
__m256i _mm256_maskz_compress_epi32( __mmask8 c, __m256i a);
它也可用于合并到现有向量底部而不是将顶部元素归零的版本。
作为一个立即洗牌,没有。
所有 x86 shuffle 都使用具有源索引的控制操作数,而不是要保留哪些元素的位图。 (vpcompressd/q 和 vpexpandd/q 除外)。或者他们使用隐式控制,例如 _mm256_unpacklo_epi32,它从 2 个输入(低半和高半的通道内)交错 32 位元素。
如果您要提供带有控制操作数的随机播放,那么如果任何元素都可以在任何位置结束,这通常是最有用的。所以输出不必与输入的顺序相同。您的 compress shuffle 没有该属性。
此外,随机播放硬件自然需要为每个输出元素提供源索引。我的理解是,每个输出元素都由它自己的 MUX(多路复用器)馈送,其中 MUX 采用 N 个输入元素和一个二进制选择器来选择输出哪一个。 (当然,它与元素宽度一样宽。)请参阅Where is VPERMB in AVX2?,了解有关构建多路复用器的更多讨论。
如果控制操作数采用某种格式而不是选择器列表,则需要进行预处理,然后才能将其馈送到 shuffle 硬件。
对于立即数,格式是 2x1 位或 4x2 位字段,或者 _mm_bslli_si128 和 _mm_alignr_epi8 的字节移位计数。或insertps 的索引 + 归零位掩码。没有立即数大于 8 位的 SIMD 指令。 大概这让硬件解码器变得简单。
(或 1x1 位的 vextractf128 xmm, ymm, 0 or 1,事后看来,没有立即处理会更好。与 0 一起使用总是比 vmovdqa xmm, xmm 差。虽然 AVX512 确实对 vextractf32x4 使用相同的操作码为 1x2 位立即数加上 EVEX 前缀,所以也许这对解码器的复杂性有一些好处。无论如何,没有选择器字段宽于 2 位的立即洗牌,因为 8x 3 位将是 24位。)
对于像_mm256_shuffle_ps (vshufps ymm, ymm, ymm, imm8) 这样的更宽的 4x2 通道内随机播放,两个通道会重复使用相同的 4x2 位选择器模式。对于像 _mm256_shuffle_pd (vshufpd ymm, ymm, ymm, imm8) 这样更宽的 2x1 通道内随机播放,我们得到 4x 1 位立即字段,它们仍然选择通道内。
有 4 个 2 位选择器 vpermq 和 vpermpd 的车道交叉洗牌。它们的工作方式与 pshufd xmm (_mm_shuffle_epi32) 完全相同,但在 256 位寄存器中使用 4x qword 元素,而不是在 128 位寄存器中使用 4x dword 元素。
至于缩小/只关心部分输出:
一个普通的立即数需要 4 个 3 位选择器来索引 8 个 32 位源元素的每个索引之一。但更可能的是 8x 3 位选择器 = 24 位,因为为什么要设计一个只能写入半宽度输出的 shuffle 指令? (vextractf128 xmm, ymm, 1 除外)。
一般来说,更精细的洗牌的范例是采用控制向量,而不是一些时髦的立即编码。
AVX512 确实添加了一些缩小洗牌,例如 VPMOVDB xmm/[mem], x/y/zmm 将 32 位元素截断(或有符号/无符号饱和)到 8 位。 (并且所有其他尺寸组合都可用)。
它们很有趣,因为它们可用于内存目的地。这可能是由一些没有 AVX512VL 的 CPU(如 Xeon Phi KNL / KNM)推动的,因此它们可以仅使用带有 ZMM 向量的 AVX512 指令。不过,它们有 AVX1 和 2,因此您可以压缩成 xmm reg 并使用普通的 VEX 编码存储。但它确实允许使用 AVX512F 进行窄字节屏蔽存储,只有在 XMM 寄存器中有打包数据时才能使用 AVX512BW。
有一些像 shufps 这样的 2-input shuffle 分别处理输出的低半部分和高半部分,例如输出的低半部分可以从第一个源寄存器的元素中选择,输出的高半部分可以从第二个源寄存器的元素中选择。