|
以下作为我个使用cotex M7内核统计的一些特性:
一 内核行为分析
实例代码1:
FPU_Test: //double FPU_Test (double a_mul,double b_mul,double c_add);
PUSH{R4-R7}
MOV R0, #1024 //减少循环次数,避免缓存影响
// 初始化完全独立的寄存器组
LDR R1,=8234
MOV R6,R0
MOV R7,R1
PLI [LR]
FPU_Test_Loop_Opt:
// 8组完全独立的 VFMA 操作
VFMA.F32 S2, S1, S0 // 组1
SMLAL R3,R2,R1,R0
VFMA.F32 S3, S1, S0 // 组2 - 完全独立
SUBS R4,R4, #1
SHADD16 R5,R5,R0
SMLAL R3,R2,R1,R0
VFMA.F32 S4, S1, S0 // 组3 - 完全独立
SHADD16 R4,R4,R1
SUBS R5,R5, #1
SMLAL R3,R2,R1,R0
VFMA.F32 S5, S1, S0 // 组4 - 完全独立
SHADD16 R5,R5,R0
SUBS R4,R4, #1
SMLAL R3,R2,R1,R0
VFMA.F32 S6, S1, S0 // 组5 - 完全独立
SUBS R0,R0, #1
BNE FPU_Test_Loop_Opt
POP{R4-R7}
BX LR
以上为keilv5 MDK V5.23 编译器语法
1:使用VFMA(浮点区域指令)与使用SMLAL(整数区域指令)能够让处理器进行双发射(在cotex M7权威手册里亦有记载)
但是,注意需注意顺序即:
VFMA.F32 S2, S1, S0 // 组1
SMLAL R3,R2,R1,R0
VFMA.F32 S4, S1, S0 // 组3 - 完全独立
SHADD16 R4,R4,R1
是能够正常双发射
VFMA.F32 S2, S1, S0 // 组1
SMLAL R3,R2,R1,R0
SHADD16 R4,R4,R1
VFMA.F32 S4, S1, S0 // 组3 - 完全独立
以上只能单发射,而不能双发射
SHADD16 R4,R4,R1
SUBS R5,R5, #1
SMLAL R3,R2,R1,R0
VFMA.F32 S5, S1, S0 // 组4 - 完全独立
以上可以双发射
总结为:在使用浮点+整数指令时,下一条指令如果需要使用整数并且需要使用寄存器,那么MCU则不支持双发射,如果为浮点,即可成功双发射.
若持续为整数指令,那么在数据无依赖的情况下,即可双发射,否者只能单发射,或者阻塞 (均不包含除法指令)
1 针对于除法指令,在mcu上能不用就不用,无符号/有符号整数除法平均会消耗10个周期左右,除非你的结果较小,例如小于256.那么可以在较短的时间里得出结果,对于双精度浮点除法,通常需要14-16个时钟周期,单精度需要8-12个周期.除法指令是阻塞运行的,不支持单周期的吞吐量.除法比较特殊,即使数据无依赖也不行
2 在cotex-m7内核上,大部分都会有支持双精度浮点,但是,双精度浮点一般比单精度慢2-8倍,不同指令有着不同的效率,如VADD.F64就最快,2周期的吞吐量,基本与VADD.F32 的单周期差不了太多,对于像VFMA.F32(实例代码中的指令)为单周期吞吐量. VFMA.F64不支持单周期吞吐量,执行一条需要7个周期,并且不支持与其他指令双发射,包括大部分的.F64的运算指令(像VMOV.F64这种执行时间为2周期,与双精度与单精度无关,执行周期按位宽/32bit)都不支持与其他指令双发射
二 性能优化:
对于需要性能优化的场景来说,手动添加(C#) __ASM volatile{ "PLI [这里填C里的一个变量]");用于提前预加载(提示内核等会儿要使用)需要执行的指令,可以在跳转后更快的执行,通常用于执行动态代码,例如:
__ASM volatile (
"push{r0,r1}\n"
"MOV R0,0X01 \n"
"ISB SY\n"
"PLI [R0]\n"
"ISB SY\n"
"pop {r0,r1}\n"
);
__ASM volatile (
"push {r0-r3,r12,lr} \n"
"MOV R0,0X01 \n"
"BLX R0 \n"
"pop {r0-r3,r12,lr} \n"
);
中,将动态代码存放于ITCM内存中,代码就是实例代码1,存放地址0x00
对于需要跳转动态代码时,保存寄存器是一个必不可少的的操作,通常使用堆栈保存,通常建议堆栈始终为8字节对齐(在cotex M系列内核权威手册里亦有记载) 所以在使用push的时候,建议一次性压入两个寄存器(64位)保持一直为8字节对齐.对于未对其的情况下,我也测试过了,首先效率会下降,对齐的情况,入栈8个字节仅需一个周期,无阻塞的发射,即可执行接下来的指令,未对其情况下,与使用STM SP!{ }是等效的,需要1个周期解析指令+2个周期存入数据.
其次未8字节对齐容易导致未定义行为,进入HardFault_Handler,这个问题隐藏的很隐蔽,使用Jlink单步调试是无法复现问题.(我没记错的话,权威手册里应该有记载,不过容易忽略,这里就提个醒)
对于地址跳转,一定要保证,需要跳转的地址的最低位一定要为1, 例如你需要跳转到0x0800346,那么你实际要写入PC指针的地址一定是0x0800347,而不是0x0800346,否则也会导致未定义行为,进入HardFault_Handler,同样使用jlink无法复现,单步无法发现问题.
先记录到这里,如有新的勘误点会在更新,附件为cotex M4系列内核汇编编程手册,全册中文,对于初学者来说很有用处
|