我正致力于在Android上编写几个实时DSP算法,所以我决定直接在汇编中编程ARM,以尽可能优化所有内容,并使数学最小化。一开始,我得到的速度基准并不是很有意义,所以我开始阅读管道危险,双重问题能力等。我仍然对我得到的一些数字感到困惑,所以我把它们贴在这里,希望有人能解释一下为什么我得到了我得到的。我特别感兴趣的是,为什么NEON在不同的数据类型上运行计算要花费不同的时间,尽管它声称在一个周期内完成每个操作。我的发现如下:
我正在使用一个非常简单的循环进行基准测试,并且我运行了2,000,000次迭代。下面是我的函数:
hzrd_test:
@use received argument an number of iterations in a loop
mov r3 , r0
@come up with some simple values
mov r0, #1
mov r1, #2
@Initialize some NEON registers (Q0-Q11)
vmov.32 d0, r0, r1
vmov.32 d1, r0, r1
vmov.32 d2, r0, r1
...
vmov.32 d21, r0, r1
vmov.32 d22, r0, r1
vmov.32 d23, r0, r1
hzrd_loop:
@do some math
vadd.s32 q0, q0, q1
vadd.s32 q1, q0, q1
vadd.s32 q2, q0, q1
vadd.s32 q3, q0, q1
vadd.s32 q4, q0, q1
vadd.s32 q5, q0, q1
vadd.s32 q6, q0, q1
vadd.s32 q7, q0, q1
vadd.s32 q8, q0, q1
vadd.s32 q9, q0,s q1
vadd.s32 q10, q0, q1
vadd.s32 q11, q0, q1
@decrement loop counter, branch to loop again or return
subs r3, r3, #1
bne hzrd_loop
@return
mov r0, r3
mov pc, lr
注意计算操作和数据类型指定为vector add (vadd
)和signed 32位int (s32
)。此操作在一定时间内完成(见下表结果)。根据这个ARM Cortex-A8文档和下面的页面,NEON中几乎所有的基本算术运算都应该在一个周期内完成,但这是我得到的:
我通过简单地替换上面循环中所有的操作和数据类型来实现它们。vadd.u32
比vadd.f32
快两倍,vmul.f32
比vmul.u32
快两倍,有什么原因吗?
干杯!div =)
哇,你的结果非常准确:
- 32位整数Q乘需要4个周期,而浮点需要2个周期。
- 32位整数Q加1周期,浮点2周期。
好实验。
也许你已经知道了,但是在为NEON:
编码时要小心。- 当NEON在做繁重的工作时,不能用ARM访问内存
- 不要将VFP指令与NEON指令混合使用。(共享的除外) 不能访问S寄存器。
- 不能从NEON寄存器转移到ARM的
以上所有这些都会引起巨大的打嗝。
祝你好运!
PS:我宁愿优化A9(稍微不同的周期时间),因为几乎所有的新设备都是A9的。ARM的A9时序图更容易读懂。: -)我猜(因为我手边没有文档链接)您遇到了管道问题。我知道FPU(错误,现在称为VFPU)与CPU处理循环的整数数学部分的管道长度不同。我看到第二个算术运算依赖于第一个算术运算,这将使任何一个管道停滞,并可能暴露您所看到的差异。
此外,我认为乘法不是整数的1个周期指令,而是2-5个周期,这取决于第二个值的msb——这里的2个周期是由于数字大小较小,这可以解释这种差异。为了验证这一点,从一个较大的乘数开始,看看它是否会随着大小的增大而变慢。
我也会验证你的代码都适合一个缓存页面只是为了消除这种可能性。
我还想看看上面的双执行部分,因为当事情交叉依赖时,那里也会发生各种各样的管道停顿。