SIMD:更通用的随机播放功能
Posted
技术标签:
【中文标题】SIMD:更通用的随机播放功能【英文标题】:SIMD: more generic shuffle function 【发布时间】:2019-10-28 20:53:59 【问题描述】:我认为 SIMD shuffle fucntion 是 not real shuffle for int32_t
case 左右部分将分开洗牌。
我想要一个真正的随机播放功能如下:
假设我们得到了__m256i
,我们想要洗牌 8 int32_t
。
__m256i to_shuffle = _mm256_set_epi32(17, 18, 20, 21, 25, 26, 29, 31);
const int imm8 = 0b10101100;
__m256i shuffled _mm256_shuffle(to_shuffle, imm8);
我希望shuffled = 17, 20, 25, 26, -, -, -, -
,其中-
代表不相关的值,它们可以是任何值。
所以我希望将1
设置位的int
放在shuffled
中。
(在我们的例子中:17、20、25、26 坐在imm8
中带有1
的位置)。
这个功能是Intel提供的吗? 怎样才能有效地实现这样的功能?
编辑:-
可以忽略。只需要设置位 1
的 int。
【问题讨论】:
Gcc 的__builtin_shuffle
和 clang 的 __builtin_shufflevector
很接近,尽管它们没有您要求的“无关”标记。在编译器中生成合理的指令序列已经是很多工作,占位符会使它变得更加困难。
-
可以忽略。示例中的shuffled = 17, 20, 25, 26
就可以了
您可以访问 AVX2 吗?然后你可以使用_mm256_permutevar8x32_epi32
。
__m256i
被定义为(在 GNU C 中)typedef __m256i long long __attribute__((vector_size(32), may_alias))
。即作为 4x long long
的向量。您的初始化程序有太多元素。也许你想要_mm256_setr_epi32( 17, 18, ...)
顺便说一句,你的即时是倒退的。 0b1...
中的第一位是 high 位。编辑时,我使用了_mm256_set_epi32
,它采用从高到低的顺序排列元素(就像英特尔通常的矢量布局图,左移向左)。但是,您想要的结果没有意义;它在矢量的 top 处具有所需的元素。我认为您实际上想要_mm256_setr_epi32
和位反转立即数。
【参考方案1】:
(我假设您立即向后退(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 分别处理输出的低半部分和高半部分,例如输出的低半部分可以从第一个源寄存器的元素中选择,输出的高半部分可以从第二个源寄存器的元素中选择。
【讨论】:
以上是关于SIMD:更通用的随机播放功能的主要内容,如果未能解决你的问题,请参考以下文章