函数: static void conv5x5s1_neon(const Mat& bottom_blob, Mat& top_blob, const Mat& _kernel, const Mat& _bias, const Option& opt)
说明:这个卷积实现有三个分支,
- 使用NEON aarch64指令的分支,
- 使用NEON arm32指令的分支:
- 不使用NEON指令的分支;
当然,NEON aarch64指令的分支性能最高,代码也更难懂一些。
输入:
- Bottom_blob:;
- _kernel:
- _bias:
- opt
输出:
- Top_blob:;
返回值:void
非NEON实现的处理过程:
它并没有像caffe那样先进行按列展开,然后调用openblas的GEMM的方式完成。而是,采用最原始的4层嵌套循环,完成kernel在bottom blob data上滑动完成,具体如下所示:
- 因为输出的每个channel的Mat,是由该channel对应的kernel在输入的blob data上滑动得到的;因此,该输出的每个channel的Mat之间是相互独立的。所以,可以采用openmp进行并行化处理。也把不同output channel的遍历放在嵌套循环的最顶层;因此,当前channel对应的kernel,记为kernel_curOutChs, 相对于kernel起始地址的偏移为:(CurOutChannelNum)*(MaxInChannelNum)*5*5。
- 因为输入 的channel与当前kerne(记为kernel_curOutChs)的channel(记为kernel_curOutChs_curInChs)是对应的,但是,对于当前output channel的当前位置的value是所有input channel与对应的kernel_curOutChs_curInChs窗口点乘后求和得到的。因此,对于output 结果来说,不同input channel之间有求和的关联关系,因此不能并行化。因此,把不同input channel的遍历放在了第二层。当前input channel的kernel(kernel_curOutChs_curInChs)对应于当前output channel kernel(kernel_curOutChs)的起始位置偏移为:CurInChannelNum * 25;
- 遍历output h,再嵌套遍历output w,进行滑动窗口计算。
因此,从上面的实现上可以看到,如果不采用NEON方式去实现的话,它的性能是很差的,比caffe采用列展开然后openblas矩阵相乘还要差的多。
所以,NCNN的价值在于NEON的实现。