64-bit DSP filtering performance optimization on Neon/RPi

Viewed 257

I have a 32-bit C++ DSP audio processing project on Analog Devices Sharc DSP processor and need to move it to 64-bit processing that has been now for some time available for embedded use-cases with ARM AArch64.

I am considering two alternatives:

  • either to use my own custom implementation of FIR and IIR filtering or
  • to go for some library functions optimized for AArch64 and Neon.

I have quite CPU-intensive processing on top of 64-bit precision. I also need to gain much more processing power as currently Sharc performance is also a bottleneck. The IIR and FIR functions should provide 64-bit real-time, block-based signal processing.

My target platform is Raspberry Pi, 3B+ maybe 4. The sort of functions I need is provided e.g. in the CMSIS library as arm_biquad_cascade_df2T_f64() (it actually works together with a supplementary init function that implements the state array needed to process data in a block-based manner). And the library funcs seems to work with 64-bit. But I have doubts if they are suitable and optimized for AArch64 as generally CMSIS is labeled 32-bit, Ne10 similarly.

I am exploring the custom code path, my questions are:

  • what kind of Neon and AArch64 specific optimizations are possible
  • which magnitude of performance improvement can be expected compared to plain C implementation of block-based biquad function

Or maybe it is enough when it is left to compiler optimization and use of Neon?

1 Answers

If you've got the choice, for performance you'll definitely want to choose AArch64 over 32bit Neon implementations. AArch64 has got more/wider vector registers. And the CPUs profit much more from out-of-order execution, because AArch64 dropped the ubiquitous conditional execution of the 32bit instruction set, which is prone to cause extra dependencies between instructions through the condition flags.

My personal conclusions from a specific optimisation task recently:

  • clang managed better autovectorisation than gcc
  • clang 10 had better autovectorisation than clang 8
  • clang's tuning for a specific Cortex core was impressive
  • helpful clang flags: -Rpass=loop-vectorize -Rpass-missed=loop-vectorize -Rpass-analysis=loop-vectorize
  • neither compiler managed to overcome the lack of AArch64 vector gather-loads
  • manually vectorised code still outperformed the auto-vectorisation by 2x
  • programming using the ARM vector intrinsics is hampered by poorly indexed documentation, and ARM renaming all vector instructions from ARMv7 to AArch64
  • use llvm-mca (https://llvm.org/docs/CommandGuide/llvm-mca.html), it's invaluably helpful

Mind that these experiences shouldn't be generalised liberally, it was a specific task.

How much performance you gain with a manually optimised implementation depends on the success of the auto-vectoriser with your specific plain C code.

I started with a plain C implementation, had it auto-vectorised, studied the llvm-mca output, found weaknesses of the auto-vectorised code, and worked on from there.

Related