gpt4 book ai didi

c++ - 有效地计算 arm neon 中 16 字节缓冲区中不同值的数量

转载 作者:行者123 更新时间:2023-11-30 05:02:14 28 4
gpt4 key购买 nike

这是计算缓冲区中不同值数量的基本算法:

unsigned getCount(const uint8_t data[16])
{
uint8_t pop[256] = { 0 };
unsigned count = 0;
for (int i = 0; i < 16; ++i)
{
uint8_t b = data[i];
if (0 == pop[b])
count++;
pop[b]++;
}
return count;
}

是否可以通过加载到 q-reg 并施展魔法,以某种方式在 neon 中有效地完成此操作?或者,我是否可以有效地说 data 具有相同的所有元素,或者仅包含两个或两个以上的不同值?

例如,使用 vminv_u8vmaxv_u8 我可以找到最小和最大元素,如果它们相等,我知道 data 具有相同的元素.如果不是,那么我可以用最小值 vceq_u8 和用最大值 vceq_u8 然后 vorr_u8 这些结果并比较我所有的 1-s在结果中。基本上,在 NEON 中,它可以通过这种方式完成。有什么想法可以让它变得更好吗?

unsigned getCountNeon(const uint8_t data[16])
{
uint8x16_t s = vld1q_u8(data);
uint8x16_t smin = vdupq_n_u8(vminvq_u8(s));
uint8x16_t smax = vdupq_n_u8(vmaxvq_u8(s));
uint8x16_t res = vdupq_n_u8(1);
uint8x16_t one = vdupq_n_u8(1);

for (int i = 0; i < 14; ++i) // this obviously needs to be unrolled
{
s = vbslq_u8(vceqq_u8(s, smax), smin, s); // replace max with min
uint8x16_t smax1 = vdupq_n_u8(vmaxvq_u8(s));
res = vaddq_u8(res, vaddq_u8(vceqq_u8(smax1, smax), one));
smax = smax1;
}
res = vaddq_u8(res, vaddq_u8(vceqq_u8(smax, smin), one));
return vgetq_lane_u8(res, 0);
}

通过一些优化和改进,或许可以用 32-48 条 neon 指令处理一个 16 字节的 block 。这可以在 ARM 上做得更好吗?不太可能

我问这个问题的一些背景。当我在研究一种算法时,我正在尝试不同的方法来处理数据,但我不确定最后我会使用什么。可能有用的信息:

  • 每个 16 字节 block 的不同元素数
  • 每 16 字节 block 重复次数最多的值
  • 平均每 block
  • 每个 block 的中位数
  • 光速?..开个玩笑,它不能用 neon 从 16 字节 block 计算:)

所以,我正在尝试一些东西,在我使用任何方法之前,我想看看该方法是否可以得到很好的优化。例如,每个 block 的平均值基本上是 arm64 上的 memcpy 速度。

最佳答案

如果您期望有很多重复项,并且可以高效使用vminv_u8 获得水平最小值,这可能比标量更好.或者不是,也许 NEON->ARM 因循环条件而停顿杀死它。 >.< 但是应该可以通过展开来缓解这种情况(并在寄存器中保存一些信息以确定您超出了多少)。

// pseudo-code because I'm too lazy to look up ARM SIMD intrinsics, edit welcome
// But I *think* ARM can do these things efficiently,
// except perhaps the loop condition. High latency could be ok, but stalling isn't

int count_dups(uint8x16_t v)
{
int dups = (0xFF == vmax_u8(v)); // count=1 if any elements are 0xFF to start
auto hmin = vmin_u8(v);

while (hmin != 0xff) {
auto min_bcast = vdup(hmin); // broadcast the minimum
auto matches = cmpeq(v, min_bcast);
v |= matches; // min and its dups become 0xFF
hmin = vmin_u8(v);
dups++;
}
return dups;
}

这会将唯一值变成 0xFF,一次一组重复项。

循环携带的dep链通过v/hmin停留在 vector 寄存器中;只有循环分支需要 NEON->integer。


最小化/隐藏 NEON->整数/ARM 惩罚

按 8 展开,hmin 上没有分支,将结果留在 8 个 NEON 寄存器中。然后转移这8个值; back-to-back transfers of multiple NEON registers to ARM only incurs one total stall (Jake 测试的 14 个周期中的任何一个。)乱序执行也可以隐藏一些对这个停顿的惩罚。然后用完全展开的整数循环检查这 8 个整数寄存器。

将展开因子调整得足够大,这样您通常就不需要对大多数输入 vector 进行另一轮 SIMD 运算。如果几乎所有 vector 最多有 5 个唯一值,则展开 5 而不是 8。


不是将多个 hmin 结果转换为整数,而是用 NEON 计数。如果您可以使用 ARM32 NEON 部分寄存器技巧免费将多个 hmin 值放入同一个 vector 中,那么将其中的 8 个值洗牌到一个 vector 中并比较不等于0xFF。然后水平添加该比较结果以获得 -count

或者,如果您在单个 vector 的不同元素中有来自不同输入 vector 的值,则可以使用垂直运算一次为多个输入 vector 添加结果,而无需水平运算。


几乎肯定有优化空间,但我不太了解 ARM,或 ARM 性能细节。 NEON 很难用于任何条件,因为 NEON-> 整数的性能损失很大,这与 x86 完全不同。 Glibc has a NEON memchr在循环中使用 NEON->integer,但我不知道它是否使用它或者它是否比标量更快。


加速对标量 ARM 版本的重复调用:

每次都将 256 字节缓冲区清零的代价很高,但我们不需要这样做。 使用序列号以避免需要重置:

  • 在每组新元素之前:++seq;
  • 对于集合中的每个元素:

    sum += (histogram[i] == seq);
    histogram[i] = seq; // no data dependency on the load result, unlike ++

您可以将直方图设为 uint16_tuint32_t 的数组,以避免在 uint8_t seq 回绕时需要重新归零。但随后它会占用更多的缓存空间,因此也许只是将每 254 个序列号重新归零是最有意义的。

关于c++ - 有效地计算 arm neon 中 16 字节缓冲区中不同值的数量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49993056/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com