SIMD - AVX - 使用非零值而不是最高位进行屏蔽

SIMD - AVX - masking with non-zero value instead of highest bit

我有 AVX(没有 AVX2 或 AVX-512)。我有一个具有 32 位值的向量(仅使用 4 个最低位,其余始终为零):

[ 1010, 0000, 0000, 0000, 0000, 1010, 1010, 0000]

在内部,我将矢量保持为 __m256,因为按位运算并且这些位表示 "float numbers"。我需要从向量中导出单个 8 位数字,其中 1 表示非零位,0 表示零位。

所以对于上面的例子,我需要 8 位数:10000110

我想使用 _mm256_cmp_ps 然后 _mm256_movemask_ps。但是,对于 cmp,我不知道它是否会正常工作,如果数字不是完全浮点数并且可以是任何 "junk"。在这种情况下,哪个操作数用于 cmp?

或者有其他解决办法吗?

从概念上讲,您所做的应该有效。高 24 位为零的浮点数是有效的浮点数。然而,它们是非正规的。

虽然应该可以,但存在两个潜在问题:

  1. 如果 FP 模式设置为将非正规化刷新为零,那么它们都将被视为零。 (因此,打破了这种做法)
  2. 因为这些是非规范化的,所以您最终可能会遭受巨大的性能损失,具体取决于硬件是否可以原生处理它们。

替代方法:

由于高24位为零,可以归一化。然后做浮点比较

(警告:未经测试的代码)

int to_mask(__m256 data){
    const __m256 MASK = _mm256_set1_ps(8388608.);  //  2^23
    data = _mm256_or_ps(data, MASK);
    data = _mm256_cmp_ps(data, MASK, _CMP_NEQ_UQ);
    return _mm256_movemask_ps(data);
}

此处,data 是您的输入,其中每个 "float" 的高 24 位为零。我们称这些 8 位整数中的每一个为 x.

OR'ing with 2^23 设置浮点数的尾数,使其成为值为 2^23 + x.

的规范化浮点数

然后您将 2^23float 进行比较 - 只有当 x 不为零时才会给出 1。

备用答案,供将来 有 AVX2

的读者参考

您可以转换为 __m256i 并使用 SIMD 整数比较。

这避免了 DAZ 将这些小整数位模式视为恰好为零或微代码协助非正规(也称为次正规)输入的任何问题。

在某些 CPU 上 vcmpeqdvpmovmskps 之间可能有 1 个额外的旁路延迟周期,但您仍然领先,因为整数比较的延迟低于 FP 比较。

int nonzero_positions_avx2(__m256 v)
{
    __m256i vi = _mm256_castps_si256(v);
    vi = _mm256_cmpeq_epi32(vi, _mm256_setzero_si256());
    return _mm256_movemask_ps(_mm256_castsi256_ps(vi));
}