NEON 代码在 armeabi-v7a 上比标准 C 代码更快,但在 arm64-v8a 上更慢

meb*_*jas 1 c optimization performance simd neon

我正在尝试hello neon提供的示例,android/ndk-samples并在两台设备上测试了 fir 过滤器演示,一台设备支持ABI armeabi-v7a,另一台设备支持arm64-v8aABI。

默认情况下,JNI 代码会失败arm64-v8a,但这可以通过一些调整来解决。现在,当我最终在两个设备上运行比较代码(具有不同规格)时,我得到以下结果

armeabi-v7a设备 - 四核,32 位

C Version:      182.47 ms
Neon Version:   69.782 ms (2.62x faster)
Run Code Online (Sandbox Code Playgroud)

arm64-v8a设备 - 八核,64 位

C Version:      10.189 ms
Neon Version:   19.4836 ms (0.52295x faster)
Run Code Online (Sandbox Code Playgroud)

问题

为什么这个霓虹灯版本的速度会变慢arm64-v8a

(我对 NEON 和 SIMD 相当陌生)

链接到内部代码 - cpp/helloneon-intrinsics.c

Soo*_*nts 5

为什么这个 neon 版本在 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, kernel_vec, 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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

如果使用 GCC 构建,请确保-O3 -fno-tree-vectorize编译器自动将最后一个循环与余数向量化,从而无缘无故地膨胀代码。使用该命令行开关,代码看起来很合理