计算__mm256向量中非零项数的最快方法是什么?

时间:2017-11-14 17:19:20

标签: algorithm vector simd avx avx2

我编写了一个算法,使用英特尔内部函数并行执行多个单精度操作。算法每次迭代的结果都是单个256位向量(__m256)中非零项的数量。

例如:

 00000000  FFFFFFFF  00000000  00000000  00000000  FFFFFFFF  FFFFFFFF  FFFFFFFF

其中迭代结果为4。

计算向量中非零项数的最快方法是什么?

目前我正在做这样的事情:

float results[8];
_mm256_storeu_ps(results, result_vector);

int count = 0;
for (uint32_t idx = 0; idx < 8; ++idx)
{
    if (results[idx] != 0)
    {            
        ++count;
    }
}

这种方法很好但我不知道是否有更有效的方法可以做到这一点,也许是一种不涉及商店的方法。

1 个答案:

答案 0 :(得分:9)

硬件popcnt指令是您最好的选择。它很快,vmovmskps也非常有效,可以将每个元素的高位作为整数位掩码。 (比较/ movemask是分析矢量比较结果的标准方法,或者将其用于index a lookup table of shuffle masks)。

movemask / popcnt可以是有用的when left-packing,用于将目标指针增加你存储的元素数量(在洗牌后)。

#include <immintrin.h>

// use only with compare-results.
// or to count elements with their sign-bit set
unsigned count_true(__m256 v) {
    unsigned mask = _mm256_movemask_ps(v);
    return _mm_popcnt_u32(mask);
}

popcnt与AVX有一个单独的功能位,所以理论上可能有一个带有AVX的CPU(或虚拟机)而不是硬件popcnt,但实际上我不担心它。 (popcnt引入了SSE4.2,AVX意味着SSE4.2)

即使您希望将结果放在向量寄存器中,vmovmskps / popcnt / movd也可能是比使用整数加法水平添加0 / -1元素更好的序列。这将需要3个shuffle / add步骤将8个元素减少到1,并且你会有一个负数。

我大多提到这一点,因为将比较结果视为整数0 / -1在某些情况下很有用。例如有条件地增加计数器向量,cmpps / psubd就可以了。 (0 + x = x,所以假元素不变。)