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/qvpexpandd/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 位选择器 vpermqvpermpd 的车道交叉洗牌。它们的工作方式与 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:更通用的随机播放功能的主要内容,如果未能解决你的问题,请参考以下文章

歌曲播放的随机算法的探讨

Swift 1.2 中未正确实现随机播放功能

调用时不播放随机音频列表

选择与随机播放,Python

随机播放字典中的数据以获取测试和训练数据

SoundCloud嵌入小部件,希望每次都能随机播放一首歌