【问题标题】:How to optimize histogram statistics with neon intrinsics?如何使用霓虹内在函数优化直方图统计?
【发布时间】:2016-11-24 21:16:46
【问题描述】:

我想用neon内在函数优化直方图统计代码。但我没有成功。这是c代码:

#define NUM (7*1024*1024)
uint8 src_data[NUM];
uint32 histogram_result[256] = {0};
for (int i = 0; i < NUM; i++)
{
    histogram_result[src_data[i]]++;
}

Historam statistic 更像是串行处理。使用 neon 内在函数很难优化。有人知道如何优化吗?提前致谢。

【问题讨论】:

  • 几乎不可能使用任何类型的 SIMD 优化直方图,包括 Neon。唯一值得注意的例外是 AVX-512。
  • 就像 Paul 所说,您的算法需要收集加载和分散存储,而大多数 SIMD 指令集都不提供。
  • 相关:微小的直方图(如 4 个桶)可以使用 count[0] += (arr[i] == 0),您可以使用 SIMD 打包比较对其进行矢量化 - Micro Optimization of a 4-bucket histogram of a large array or list

标签: neon intrinsics


【解决方案1】:

您不能直接对存储进行矢量化,但可以将它们流水线化,并且可以在 32 位平台上对地址计算进行矢量化(在 64 位平台上的程度较小)。

您要做的第一件事(实际上并不需要 NEON 受益)是展开直方图数组,以便您可以一次获得更多数据:

#define NUM (7*1024*1024)
uint8 src_data[NUM];
uint32 histogram_result[256][4] = {{0}};
for (int i = 0; i < NUM; i += 4)
{
    uint32_t *p0 = &histogram_result[src_data[i + 0]][0];
    uint32_t *p1 = &histogram_result[src_data[i + 1]][1];
    uint32_t *p2 = &histogram_result[src_data[i + 2]][2];
    uint32_t *p3 = &histogram_result[src_data[i + 3]][3];
    uint32_t c0 = *p0;
    uint32_t c1 = *p1;
    uint32_t c2 = *p2;
    uint32_t c3 = *p3;
    *p0 = c0 + 1;
    *p1 = c1 + 1;
    *p2 = c2 + 1;
    *p3 = c3 + 1;
}

for (int i = 0; i < 256; i++)
{
    packed_result[i] = histogram_result[i][0]
                     + histogram_result[i][1]
                     + histogram_result[i][2]
                     + histogram_result[i][3];
}

请注意,p0p3 永远不能指向同一个地址,因此重新排列它们的读写顺序就可以了。

由此,您可以使用内在函数将 p0 的计算向量化为 p3,并且您可以对最终循环进行向量化。

首先测试它(因为我没有!)。然后,您可以尝试将数组构造为 result[4][256] 而不是 result[256][4],或者使用更小或更大的展开因子。

对此应用一些 NEON 内在函数:

uint32 histogram_result[256 * 4] = {0};
static const uint16_t offsets[] = { 0x000, 0x001, 0x002, 0x003,
                                    0x000, 0x001, 0x002, 0x003 };
uint16x8_t voffs = vld1q_u16(offsets);
for (int i = 0; i < NUM; i += 8) {
    uint8x8_t p = vld1_u8(&src_data[i]);
    uint16x8_t p16 = vshll_n_u8(p, 16);
    p16 = vaddq_u16(p16, voffs);
    uint32_t c0 = histogram_result[vget_lane_u16(p16, 0)];
    uint32_t c1 = histogram_result[vget_lane_u16(p16, 1)];
    uint32_t c2 = histogram_result[vget_lane_u16(p16, 2)];
    uint32_t c3 = histogram_result[vget_lane_u16(p16, 3)];
    histogram_result[vget_lane_u16(p16, 0)] = c0 + 1;
    c0 = histogram_result[vget_lane_u16(p16, 4)];
    histogram_result[vget_lane_u16(p16, 1)] = c1 + 1;
    c1 = histogram_result[vget_lane_u16(p16, 5)];
    histogram_result[vget_lane_u16(p16, 2)] = c2 + 1;
    c2 = histogram_result[vget_lane_u16(p16, 6)];
    histogram_result[vget_lane_u16(p16, 3)] = c3 + 1;
    c3 = histogram_result[vget_lane_u16(p16, 7)];
    histogram_result[vget_lane_u16(p16, 4)] = c0 + 1;
    histogram_result[vget_lane_u16(p16, 5)] = c1 + 1;
    histogram_result[vget_lane_u16(p16, 6)] = c2 + 1;
    histogram_result[vget_lane_u16(p16, 7)] = c3 + 1;
}

使用展开的直方图数组 x8 而不是 x4,您可能希望使用八个标量累加器而不是四个,但您必须记住,这意味着八个计数寄存器和八个地址寄存器,这比 32 位 ARM 有更多的寄存器(因为你不能使用SP和PC)。

不幸的是,地址计算在 NEON 内部函数手中,我认为编译器无法安全地推断它如何重新排序读取和写入,因此您必须明确地重新排序它们并希望您以最好的方式做到这一点。

【讨论】:

  • 我喜欢这种尝试减少同一条目的多次增量的存储转发延迟的想法。但是,触摸更多内存是有问题的,首先将其全部归零。
  • result[4][256] 最后处理起来要快得多,因为您只需要垂直 SIMD 操作。即,您可以为 4 个 SIMD 元素并行计算 4 个(或 3 个或 7 个)分区。如果您正在处理大量输入数据,那么可能值得使用 result[256][4] 并且必须对每个存储桶的 N 个分区进行水平总和,如果您的数据具有导致在增量期间更好的缓存行为的分布阶段。例如运行相同的输入值会创建对同一缓存行的 4 次访问,而不是对跨度为 256*sizeof(int) 的 4 个缓存行的访问。
  • you can vectorise the calculation of p0 to p3 with intrinsics。没有用处,没有收集+分散。使用高效的 AVX2 收集(例如在 Skylake 上),进行收集加载然后手动分散可能是一个胜利,但是计算 SIMD 向量中的地址,然后将每个向量元素提取到整数寄存器以用作指针是昂贵的。更糟糕的是,您还必须将递增的计数器提取到内存中。 (不过,对于 x86,有一条指令 (pextrd) 可以将向量元素直接提取到内存中(具有立即元素索引)。
  • @PeterCordes,NEON 允许您在一次操作中从一个向量中填充两个标量寄存器,因此它只是四个通道的其中两个操作;但是延迟可能是有序内核的问题。当我实现它时(在我的日常工作中),我从src_data 中读取并在 NEON 中进行所有地址算术,然后将地址传递给标量,然后以标量进行加载、递增和存储。我认为标量路径可能更好地针对随机访问并发进行了优化,并且仅针对增量操作而通过 SIMD 是不值得的。
  • 我喜欢认为(但我懒得测试)因为热点区域是连续的,所以从 1kB 到 4kB 的扩展可以忽略不计。当相同的额外 3kB 随机分布在内存中时,它更容易出现混叠,这很容易将事情推下悬崖。
猜你喜欢
  • 2011-08-08
  • 1970-01-01
  • 1970-01-01
  • 2012-01-01
  • 2016-01-30
  • 1970-01-01
  • 2014-07-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多