【问题标题】:What is the fastest way to count the number of nonzero entries in an __mm256 vector?计算 __mm256 向量中非零条目数的最快方法是什么?
【发布时间】:2018-04-27 18:01:28
【问题描述】:

我编写了一个算法,该算法使用英特尔内在函数并行执行多个单精度运算。我的算法每次迭代的结果是单个 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;
    }
}

这种方法效果很好,但我想知道是否有更有效的方法,也许不涉及商店。

【问题讨论】:

  • 非零条目是否保证为0xFFFFFFFF?如果是这样,一个想法是使用掩码与以隔离每个 32 位部分中的最低有效位,然后应用绝对差之和。
  • 或者只与零比较 (_mm256_cmp_ps),提取位掩码 (_mm256_movemask_ps) 并使用 popcnt 计算位?三个指令。
  • 如果它们已经是 0 / 0xFFF...(即比较的结果),您可以跳过 cmpps 步骤,只需 movemask / popcnt。
  • @PaulR 我不知道 movemask 内在; that + popcnt 完美运行,谢谢!
  • @PeterCordes 是的,结果要么是 0,要么是全 0xF,所以不需要比较

标签: algorithm vector simd avx avx2


【解决方案1】:

硬件popcnt 指令是您最好的选择。它很快,vmovmskps 也非常有效地为您提供每个元素的高位作为整数位掩码。 (compare / 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,所以假元素不变。)

【讨论】:

    猜你喜欢
    • 2011-05-16
    • 2019-11-04
    • 2017-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-20
    • 1970-01-01
    • 2011-02-25
    相关资源
    最近更新 更多