如何解决NEON 代码在 armeabi-v7a 上比标准 C 代码快,但在 arm64-v8a 上慢 armeabi-v7a 设备 - 四核,32 位arm64-v8a 设备 - 八核,64 位
我正在试用 android/ndk-samples
提供的 hello neon 示例,并在两台设备上测试了 fir 过滤器演示,一台支持 armeabi-v7a
,另一台支持 arm64-v8a
ABI。
默认情况下,JNI code 对于 arm64-v8a
失败,但可以通过一些调整来解决。现在,当我最终在两个设备上运行比较代码(具有差异规格)时,我得到以下结果
armeabi-v7a
设备 - 四核,32 位
C Version: 182.47 ms
Neon Version: 69.782 ms (2.62x faster)
arm64-v8a
设备 - 八核,64 位
C Version: 10.189 ms
Neon Version: 19.4836 ms (0.52295x faster)
问题
为什么这个霓虹灯版本的 arm64-v8a
会变慢?
(我对 NEON 和 SIMD 还很陌生)
链接到内部代码 - cpp/helloneon-intrinsics.c
解决方法
为什么这个霓虹版本对于 arm64-v8a 会变慢?
因为您链接的代码是在 2016 年为 ARMv7 编写的。
在 ARMv7 中,8 字节 NEON 寄存器可单独寻址。
ARM64 SIMD 一直是 16 字节,8 字节向量具有非平凡的性能损失。
试试这个版本:
void fir_filter_neon_arm64( short *output,const short* input,const short* kernel,size_t width,size_t kernelSize )
{
const ptrdiff_t offset = -(ptrdiff_t)kernelSize / 2;
const size_t kernelSizeAligned = ( kernelSize / 8 ) * 8;
const bool extraVector = ( kernelSize % 8 ) >= 4;
for( size_t outer = 0; outer < width; outer++ )
{
const short* const inputPtr = input + outer + offset;
// Handle stuff 16 bytes at a time.
// Using 2 independent accumulators to improve data dependency situation on these accumulators.
int32x4_t acc1 = vdupq_n_s32( 0 );
int32x4_t acc2 = vdupq_n_s32( 0 );
size_t ii = 0;
for( ; ii < kernelSizeAligned; ii += 8 )
{
int16x8_t kernel_vec = vld1q_s16( kernel + ii );
int16x8_t input_vec = vld1q_s16( inputPtr + ii );
acc1 = vmlal_s16( acc1,vget_low_s16( kernel_vec ),vget_low_s16( input_vec ) );
acc2 = vmlal_high_s16( acc2,kernel_vec,input_vec );
}
if( extraVector )
{
// The remainder was longer than 4,use SIMD for the first 4 of the remaining elements
int16x4_t kernel_vec = vld1_s16( kernel + ii );
int16x4_t input_vec = vld1_s16( inputPtr + ii );
acc1 = vmlal_s16( acc1,input_vec );
ii += 4;
}
// Add these two accumulators together
acc1 = vaddq_s32( acc1,acc2 );
// Horizontal sum into the scalar,ARM64 has an instruction for that
const int sumVector = vaddvq_s32( acc1 );
// Handle the final 0-3 elements
int sumRemainder = 0;
for( ; ii < kernelSize; ii++ )
sumRemainder += (int)( kernel[ ii ] ) * (int)( inputPtr[ ii ] );
// Store the final result
const int sum = sumVector + sumRemainder;
output[ outer ] = (short)( ( sum + 0x8000 ) >> 16 );
}
}
如果使用 GCC 构建,请确保 -O3 -fno-tree-vectorize
否则编译器会自动将最后一个循环向量化为剩余部分,无缘无故地膨胀代码。使用该命令行开关,代码 looks reasonable.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。