【发布时间】:2021-12-19 01:11:04
【问题描述】:
我正在使用 ARM NEON 内在函数实现一个简单的数组乘法。输入是一个 uint8 数组,输出是一个 uint16 数组。但是,常规的本机代码比 NEON 优化的代码要快。谁能帮我弄清楚如何改进 NEON 代码?
我的常规代码是
uint16_t scale_factor = 300;
for(int i = 0; i < output_size; i++)
{
out_16bit[i] = (uint16_t)(in_ptr[i] * scale_factor) ;
}
我的 NEON 代码是
uint16_t* out_ptr = out_16bit;
uint8_t* in_ptr = in_8bit;
uint16_t scale_factor = 300;
for(int i = 0; i < out_size/16; i++)
{
uint8x16_t in_v0 = vld1q_u8(in_ptr);
in_ptr += 16;
uint16x8_t in_16_v0 = vmovl_u8(vget_low_u8(in_v0));
uint16x8_t in_16_v1 = vmovl_u8(vget_high_u8(in_v0));
uint16x8_t res_0 = vmulq_n_u16(in_16_v0, scale_factor);
uint16x8_t res_1 = vmulq_n_u16(in_16_v1, scale_factor);
// code below takes long time
vst1q_u16(out_ptr,res_0);
vst1q_u16(out_ptr+8,res_1);
out_ptr += 16;
}
我也做了一些分析,发现如果我注释掉vst1q_u16s 或out_ptr += 16,速度很快。但如果我保持上述两个,它会很慢。所以我猜可能是因为指针的增量正在等待vst1q_u16的完成?然后我更新了NEON代码,在vst1q_u16和out_ptr+=16之间添加了一些代码,如下所示,
uint8x16_t in_v0 = vld1q_u8(in_ptr);
uint16x8_t in_16_v0 = vmovl_u8(vget_low_u8(in_v0));
uint16x8_t in_16_v1 = vmovl_u8(vget_high_u8(in_v0));
uint16x8_t res_0 = vmulq_n_u16(in_16_v0, scale_factor);
uint16x8_t res_1 = vmulq_n_u16(in_16_v1, scale_factor);
vst1q_u16(out_ptr,res_0);
vst1q_u16(out_ptr+8,res_1);
for(int i = 1; i < out_size/16; i++)
{
in_v0 = vld1q_u8(in_ptr);
in_16_v0 = vmovl_u8(vget_low_u8(in_v0));
in_16_v1 = vmovl_u8(vget_high_u8(in_v0));
out_ptr += 16;
res_0 = vmulq_n_u16(in_16_v0, scale_factor);
res_1 = vmulq_n_u16(in_16_v1, scale_factor);
vst1q_u16(out_ptr,res_0);
vst1q_u16(out_ptr+8,res_1);
}
但是这个改变没有奏效...请帮忙告诉我应该怎么做...谢谢。
【问题讨论】:
-
您是否检查过您的编译器是否自动向量化了您的“常规代码”? gcc does.
-
您在编译器中使用了哪些优化开关?
-
这里的居民可能希望知道确切的编译器命令行,然后可能希望将代码和编译器选项带到 Godbolt 以查看编译器告诉机器做什么。你可以考虑这条路。
-
"请问如何检查编译器是否进行了自动向量化?"基本上,您阅读程序集并查看它是否包含 SIMD 指令。
-
“非常快”的情况可能会产生误导。如果您删除存储,编译器可能会发现循环实际上并没有做任何事情,并删除整个事情。同样,如果您不增加
out_ptr,编译器可能会注意到只有最后一次迭代很重要,并跳过所有其他迭代。使用优化的编译器,你不能真正为每行代码计费,当然不能一一删除。
标签: arm simd intrinsics neon