我想用neon intrinsics优化直方图统计代码。但我没有成功。这是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统计更像是串行处理。使用霓虹内在函数进行优化是很困难的。有谁知道如何优化?提前感谢。
答案 0 :(得分:3)
您无法直接对存储进行矢量化,但您可以对它们进行流水线处理,并且可以在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];
}
请注意,p0
到p3
永远不会指向相同的地址,因此重新排序读取和写入就好了。
通过这种方式,您可以使用内在函数将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内在函数的地址计算,我认为编译器无法安全地推断它如何能够重新排序读取和写入,所以你必须明确地重新排序它们并希望你以最好的方式做到了。