【问题标题】:Aggregate sum for set bits in NEON SIMDNEON SIMD 中设置位的汇总和
【发布时间】:2015-08-13 11:34:24
【问题描述】:

我有一个对大量字节数组进行操作的算法。作为预处理步骤,我需要为给定索引创建一个计数,该计数是到目前为止在数组中设置的频率。

我可以使用以下(伪)代码在 C 中执行此操作:

input: uint8_t values[COUNT];
output: uint32_t bitsum[COUNT+1][8];
       (bitsum[i][x] is the counter for the x-th bit being set in
        the PRECEEDING i bytes -- this makes bitsum[0][x] == 0)

// we skip first row
for (int i=1; i < COUNT+1; i++) {
   for (int bit=0; bit < 8; bit++) {
      bitsum[i][bit] = bitsum[i-1][bit];
      if (values[i-1] & (1 << bit) != 0) {
         bitsum[i][bit]++;
      }
   }
}

不过,我希望使用 NEON SIMD 可以更快地实现这一目标。不幸的是,我对此很陌生,所以我还不能解决这个问题(还没有?)并寻求一些帮助。在 NEON 中甚至可以做到这一点吗?

更新:

试图在 C 中加快这段代码的速度,我相信以下方法是最快的(当然,不展开内部 for 循环):

// pre-calculate lookup-table
uint16_t lookup[256][8];
for (int value=0; value < 256; value++) {
   for (int bit=0; bit < 8; bit++) {
      if (value & (1 << bit) != 0) {
         lookup[value][bit]++;
      }
   }
}

// create sum
for (int i=1; i < COUNT+1; i++) {
   for (int bit=0; bit < 8; bit++) {
      bitsum[i][bit] = bitsum[i-1][bit] + lookup[values[i-1]][bit];
   }
}

这看起来对于 SIMD 来说是理想的,除了查找表访问 - 至少我在 NEON 中找不到这样做的方法。

【问题讨论】:

  • 应该是累积的吗?您的描述听起来好像应该,但示例代码没有这样做。
  • @harold:感谢您发现这一点,它确实应该是累积的。我已经修复了伪代码(显然不是优化代码)。

标签: simd neon


【解决方案1】:

您可以使用VTBLVTBX 指令在NEON 中进行表查找,但它们仅对查找条目很少的表有用。在针对 NEON 进行优化时,通常最好寻找一种在运行时计算值的方法,而不是使用表格。

在此示例中,可以直接在运行时计算查找。该功能本质上是

int lookup(int val, int bit) { return (val & (1<<bit) >> bit); }

可以轻松转换为 NEON SIMD。

因此,您的函数可以使用 NEON 内部函数实现,如下所示:

#include <arm_neon.h>

void f(uint32_t *output, const uint8_t *input, int length)
{   

    static const uint8_t mask_vals[] = {  0x1,  0x2,  0x4,  0x8,
                                         0x10, 0x20, 0x40, 0x80 };
    /* NEON shifts are left shifts, and we want a right shift,
       so use negative numbers here */
    static const int8_t shift_vals[] = { 0, -1, -2, -3, -4, -5, -6, -7 };

    /* constants we need in the main loop */
    uint8x8_t mask    = vld1_u8(mask_vals);
    int8x8_t shift    = vld1_s8(shift_vals);

    /* accumulators for results, bits 0-3 in cumul1, bits 4-7 in cumul2 */
    uint32x4_t cumul1 = vdupq_n_u32(0);
    uint32x4_t cumul2 = vdupq_n_u32(0);

    for (int i = 0; i < length; i++)
    {   
        uint8x8_t v = vld1_dup_u8(input+i);
        /* this gives 0 or 1 in each lane, depending on whether the
           appropriate bit is set */
        uint8x8_t incr = vshl_u8(vand_u8(v, mask), shift);

        /* widen to 16 bits */
        uint16x8_t incr_w = vmovl_u8(incr);

        /* increment the accumulators */
        cumul1 = vaddw_u16(cumul1, vget_low_u16(incr_w));
        cumul2 = vaddw_u16(cumul2, vget_high_u16(incr_w));
        /* store the accumulator values */
        vst1q_u32(output + i*8, cumul1);
        vst1q_u32(output + i*8 + 4, cumul2);
    }
}

免责声明:此代码可以编译,但我尚未对其进行测试。

【讨论】:

  • 完美!非常感谢,这对我帮助很大。
猜你喜欢
  • 2015-06-26
  • 2013-02-20
  • 2011-12-02
  • 1970-01-01
  • 2012-07-15
  • 2014-01-15
  • 2020-03-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多