查看: 2875|回复: 1

[分享] PowerQuad和CMSIS-DSP在计算FFT上的性能分析

[复制链接]

该用户从未签到

72

主题

80

帖子

0

版主

Rank: 7Rank: 7Rank: 7

积分
724
最后登录
2020-1-3
发表于 2019-1-10 20:37:06 | 显示全部楼层 |阅读模式
引言

笔者最近在调试LPC5500的数学计算硬件加速协处理器PowerQuad模块时,不断被问到诸如此类的问题:
PowerQuad到底能干啥?
PowerQuad跟CMSIS-DSP函数相比到底能快多少?
移植PowerQuad的实现麻烦不?用户值不值得为了获取这个计算加速,而移植现有工程到基于PowerQuad的实现?
为了给大家一个直观的体验,笔者设计了一个标准测试任务,使用统一的输入数据,以得到统一的期望输出数据作为结束,然后用PowerQuad和CMSIS-DSP的不同函数实现相同计算。通过测量执行这一个标准测试任务的运行时间,从而量化地得到不同实现方式的性能指标。

一个标准计算任务

本文中使用的算例是512点的FFT变换,输入数据为“1”和“2”的交错序列:{1,2,1,2,…,1,2 },总共512个数:
如果是定点实数计算,则使用整数“1”和“2”参与计算
如果是浮点实数计算,则使用浮点数“1.0f”和“2.0f”参与计算
如果是定点复数计算,则使用整数对“(1,0)”和“(2,0)”参与计算
如果是浮点复数计算,则使用浮点数对“(1.0f,0.0f)”和“(2.0f,0.0f)参与计算
期望的计算结果也是一个512个数的序列,除了第0个元素是768和第256个数是-256,其余元素全部为0。这个结果是比较容易理解的:
关于第0个元素的计算。
输入序列的均值明显是1.5,而根据傅里叶变换的性质,首个元素表示直流分量,而大多数的FFT计算器(例如MATLAB)都会省略掉除以N的过程,也就是均值乘以N,在本例中刚好就是1.5x512=768。
关于第256个元素的计算。
输入序列的采样周期是1,频率是1,经过512点的FFT计算得到的频率域步长就是原始频率除以N,也就是1/512。显而易见的是,输入序列信号的周期是2,频率是1/2,那么在频率域上刚好就是第256个刻度的位置。在这个频点上的信号刚好是在基础频率变化幅度为0.5,对应这个频点的能量值就是0.5,考虑省略乘以N的步骤,对应的绝对能量值是0.5x512=256,因为初始相位为负(1.0 = 1.5-0.5),所以最终的计算结果为-256.
这个计算任务在FreeMat(一个开源的MatLab风格的数学计算软件)上得到了验证。

  1. --> for (i = 1:512); x(i) = mod(i-1,2) + 1; end    % create the input array in x.
  2. --> y = fft(x)                                                           % run the fft and keep result in y
  3. --> plot([1:1:512], y)  
复制代码



计算结果如下:

下图是FreeMat计算标准计算任务的结果。


PowerQuad与CMSIS-DSP的实现比较
在实际的测试程序中,笔者设计了12个计算过程用以实现本文中设计的标准计算任务,这12个计算过程包括了使用不同有效数字位数的定点数计算函数(Q31和Q15)和浮点数的计算函数,CMSIS-DSP和PowerQuad分别实现其中的6个过程。其中,关键的计算函数有:


  1. <p>[CMSIS-DSP]</p><p>arm_cfft_f32()</p><p>arm_cfft_q31()</p><p>arm_cfft_q15()</p><p>arm_rfft_fast_f32_init() 和arm_rff_fast_f32() (官方已经不建议使用arm_rfft_f32())</p><p>arm_rfft_q31()</p><p>arm_rfft_q15()</p>
复制代码


[PowerQuad]调用PowerQuad服务时使用了MCUXpresso SDK固件库中的函数:

PQ_TransformCFFT()
PQ_TransformRFFT()
PQ_SetConfig () 被用于指定输入和输出的数据格式

这里列举使用定点复数方式完成计算的CMSIS-DSP和PowerQuad的实现代码。
CMSIS-DSP部分:


  1. <p>/* app_cmsisdsp_cfft_q31.c */
  2. #include "app.h"</p><p>extern uint32_t   timerCounter;
  3. extern q31_t      inputQ31[APP_FFT_LEN_512*2];
  4. extern q31_t     outputQ31[APP_FFT_LEN_512*2];</p><p>void App_CmsisDsp_CFFT_Q31_Example(void)
  5. {
  6.     uint32_t i;</p><p>    PRINTF("%s\r\n", __func__);</p><p>    /* input. */
  7.     for (i = 0u; i < APP_FFT_LEN_512; i++)
  8.     {
  9.         inputQ31[2*i  ] = APP_FFT_LEN_512 * (1 + i%2); /* real part. */
  10.         inputQ31[2*i+1] = 0; /* complex part. */
  11.     }</p><p>    TimerCount_Start();
  12.     arm_cfft_q31(&arm_cfft_sR_q31_len512, inputQ31, 0, 1);
  13.     TimerCount_Stop(timerCounter);</p><p>    /* output. */
  14. #if defined(APP_CFG_ENABLE_SHOW_OUTPUT_NUMBERS) && (APP_CFG_ENABLE_SHOW_OUTPUT_NUMBERS==1)
  15.     PRINTF("Output :\r\n");
  16.     for (i = 0u; i < APP_FFT_LEN_512; i++)
  17.     {
  18.         PRINTF("%4d: %d, %d\r\n", i, inputQ31[2*i], inputQ31[2*i+1]);
  19.     }
  20. #endif /* APP_CFG_ENABLE_SHOW_OUTPUT_NUMBERS */
  21.     PRINTF("Cycles : %6d  | us : %d\r\n", timerCounter, timerCounter/96u);
  22.     PRINTF("\r\n");
  23. }</p><p>/* EOF. */</p><p>
  24. PowerQuad部分:</p><p>/* app_powerquad_cfft_q31.c */
  25. #include "app.h"</p><p>extern uint32_t   timerCounter;
  26. extern q31_t      inputQ31[APP_FFT_LEN_512*2];
  27. extern q31_t     outputQ31[APP_FFT_LEN_512*2];</p><p>void App_PowerQuad_CFFT_Q31_Example(void)
  28. {
  29.     uint32_t i;</p><p>    PRINTF("%s\r\n", __func__);</p><p>    /* input. */
  30.     for (i = 0u; i < APP_FFT_LEN_512; i++)
  31.     {</p><p>#if defined(APP_CFG_POWERQUAD_ENABLE_HW_PRESCALER) && (APP_CFG_POWERQUAD_ENABLE_HW_PRESCALER==1)
  32.         inputQ31[2*i  ] = (1 + i%2); /* real part. */
  33. #else
  34.         inputQ31[2*i  ] = APP_FFT_LEN_512 * (1 + i%2); /* real part. */#endif /* APP_CFG_POWERQUAD_ENABLE_HW_PRESCALER */
  35.         inputQ31[2*i+1] = 0; /* complex part. */
  36.     }
  37.     memset(outputQ31, 0, sizeof(outputQ31)); /* clear output. */</p><p>
  38.     /* computing by PowerQuad hardware. */
  39.     {
  40.         pq_config_t pq_cfg;</p><p>        PQ_Init(POWERQUAD); /* initialize the PowerQuad hardware. */</p><p>        pq_cfg.inputAFormat = kPQ_32Bit;
  41. #if defined(APP_CFG_POWERQUAD_ENABLE_HW_PRESCALER) && (APP_CFG_POWERQUAD_ENABLE_HW_PRESCALER==1)
  42.         pq_cfg.inputAPrescale = 9; /* 2 ^9 for 512 len of input. */#else
  43.         pq_cfg.inputAPrescale = 0;
  44. #endif /* APP_CFG_POWERQUAD_ENABLE_HW_PRESCALER */
  45.         pq_cfg.inputBFormat = kPQ_32Bit;
  46.         pq_cfg.inputBPrescale = 0;
  47.         pq_cfg.tmpFormat = kPQ_32Bit;
  48.         pq_cfg.tmpPrescale = 0;
  49.         pq_cfg.outputFormat = kPQ_32Bit;
  50.         pq_cfg.outputPrescale = 0;
  51.         pq_cfg.tmpBase = (uint32_t *)0xE0000000; /* private ram. */
  52.         pq_cfg.machineFormat = kPQ_32Bit;
  53.         PQ_SetConfig(POWERQUAD, &pq_cfg);</p><p>        TimerCount_Start();
  54.         PQ_TransformCFFT(POWERQUAD, APP_FFT_LEN_512, inputQ31, outputQ31);
  55.         PQ_WaitDone(POWERQUAD);
  56.         TimerCount_Stop(timerCounter);
  57.     }</p><p>    /* output. */
  58. #if defined(APP_CFG_ENABLE_SHOW_OUTPUT_NUMBERS) && (APP_CFG_ENABLE_SHOW_OUTPUT_NUMBERS==1)
  59.     PRINTF("Output :\r\n");
  60.     for (i = 0u; i < APP_FFT_LEN_512; i++)
  61.     {
  62.         PRINTF("%4d: %d, %d\r\n", i, outputQ31[2*i], outputQ31[2*i+1]);
  63.     }
  64. #endif /* APP_CFG_ENABLE_SHOW_OUTPUT_NUMBERS */
  65.     PRINTF("Cycles : %6d  | us : %d\r\n", timerCounter, timerCounter/96u);
  66.     PRINTF("\r\n");
  67. }</p><p>/* EOF. */</p>
复制代码



计算结果通过串口终端打印出来。
下面是CMSIS-DSP实现Q31定点复数计算的结果:
QQ浏览器截图20190110203442.png


下面是PowerQuad实现Q31定点复数计算的结果:
QQ浏览器截图20190110203448.png



一些比较有意思的结论

测试工程运行在LPCXpresso5500 EVK开发板,主控芯片工作主频为96MHz,集成开发环境选用了IAR。
在测试工程中跑完了所有12个计算过程后,汇总了所有测量时间到下表中。
PowerQuad vs CMSIS-DSP计算512点FFT用时汇总表:
QQ浏览器截图20190110203504.png

注:表头中的none、low、mediem和high分别表示编译优化的级别。
根据上面的测试结果,可以得出以下一些结论。

首先毋容置疑的是,PowerQuad的计算过程整体上比CMSIS-DSP快很多。以Q31定点复数计算过程为例,CMSIS-DSP在启用硬件FPU但未启用编译优化时(最常规的调试环境)耗时6425μs,而PowerQuad在同等条件下计算耗时仅为36μs,相差178倍!

编译器优化对执行时间的影响分析。考虑到CMSIS-DSP对编译优化有很强的依赖,本文的测试用例还启动了不同的编译器优化选项。从表中可以看到,CMSIS-DSP的Q31定点复数计算过程在无优化、低优化、中优化、速度优先的高优化情况下,计算耗时分别为6425μs、4381μs、3379μs、3113μs。由此可以看出编译器优化对CMSIS-DSP函数执行的时间影响还是比较大的。

浮点数与定点数计算的对比。IAR集成开发环境中提供了FPU指令开关的编译选项。笔者抱着好奇的态度以此作为一个测试条件,期望在大多数情况下,定点数计算会比浮点数计算快很多,但实际情况多少有一点出乎意料。

同样还是Q31定点复数计算过程在无优化条件下,关闭FPU之后执行时间竟然更短了(6355μs vs 6425μs),看来强制使用定点数计算的编译方式确实能够提高定点数的计算效率。

关闭FPU之后,浮点复数的计算过程毫无意外地暴涨了好多(35236μs vs 5679μs)。但是在启用FPU的情况下,浮点复数的计算过程竟然比定点复数快了好多(5679μs vs 6425μs),无论是在何种编译优化情况下,始终是浮点数的计算过程更快,看来FPU的硬件加速过程相比于CMSIS-DSP软件算法设计的优化相比还是占了上风。

由于PowerQuad硬件不直接支持浮点FFT,所以在本测试用例中使用了PowerQuad的MatrixScale功能,做了浮点数到定点数的数字格式转换,以配合定点计算的FFT完成对浮点数的处理过程,这同样也是由PowerQuad内部的硬件计算引擎实现的。

CMSIS-DSP中也有类似的函数,例如arm_float_to_q31()和arm_q31_to_float()。笔者“顺手”也比较了这两个功能的执行时间,毫无疑问,PowerQuad依然在速度上遥遥领先(1193μs vs 31μs 和 406μs vs 31μs)。

综合以上的分析可以看出,在不同编译条件下,CMSIS-DSP的计算时间不仅比较长,并且变化幅度比较大,相对于PowerQuad极其不稳定,难以捉摸。而反观PowerQuad,无论在任何情况下,都是一如既往地快,快得那么深,那么认真。


回复

使用道具 举报

该用户从未签到

656

主题

6312

帖子

0

超级版主

Rank: 8Rank: 8

积分
19947
最后登录
2024-4-19
发表于 2019-1-11 09:21:31 | 显示全部楼层
谢谢分享!!
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /4 下一条

Archiver|手机版|小黑屋|恩智浦技术社区

GMT+8, 2024-4-20 23:05 , Processed in 0.113524 second(s), 21 queries , MemCache On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表