开启-O2优化时GCC为何重复加载FPU寄存器中的常量?
Cortex-M4下GCC -O2优化重复加载浮点常量的问题
我实现了一个基于抛物线近似的正弦余弦计算函数,针对带FPU的Cortex-M4编译。反汇编中发现一个异常:代码第34行加载了常量0.225(对应代码中的P)后,该寄存器随即被复用;到第58行又再次从内存加载同一个常量。即使把P改为局部变量,这个现象也没有改变,只有切换到-Os优化时,常量才会全程驻留在寄存器中。Cortex-M4拥有32个FPU寄存器,资源充足,为什么-O2优化下GCC不保留这个常量?
实现代码
#define sinB 1.2732395f #define C 0.40528473f #define P 0.225f // 范围在 -pi/2 ~ pi/2 内 #define cosA 1.0f // 超出 pi/2 范围 #define cos2A 3.0f #define cos2B 2.546479f // 1. 用三个关键点A、B、C计算类正弦抛物线 // 2. 计算标准抛物线与平方抛物线的差值(标准值偏大,平方值偏小)以获得更优近似 // 使用P作为平方抛物线的系数(0.225为最优值) #define M_TWOPI 6.283185307179586476925286766559f #define M_PI 3.1415926535897932384626433f #define M_PI_2 3.1415926535897932384626433f/2.0f void porabolicSinCos(float angle, float *sin, float *cos) { // 将输入角度归一化到 -PI ~ PI 范围 while (angle < -M_PI) { angle += M_TWOPI; } while (angle > M_PI) { angle -= M_TWOPI; } float anglePow2 = angle * angle; // 计算正弦值 if (angle < 0.0f) { *sin = sinB * angle + C * anglePow2; } else { *sin = sinB * angle - C * anglePow2; } // Q * y + P * y * abs(y) if (*sin < 0.0f) { *sin = P * (*sin * -*sin - *sin) + *sin; } else { *sin = P * (*sin * *sin - *sin) + *sin; } // 分三个范围计算余弦,避免角度偏移,复用angle^2的计算结果 // A=1, B=0, C=-4/pi^2 if (angle < -M_PI_2) { *cos = cos2A + cos2B * angle + C * anglePow2; } else if (angle > M_PI_2) { *cos = cos2A - cos2B * angle + C * anglePow2; } else { *cos = cosA - C * anglePow2; } if (*cos < 0.0f) { *cos = P * (*cos * -*cos - *cos) + *cos; } else { *cos = P * (*cos * *cos - *cos) + *cos; } }
反汇编代码(关键片段标注)
porabolicSinCos: vldr.32 s15, .L35 vcmpe.f32 s0, s15 vmrs APSR_nzcv, FPSCR bpl .L2 vldr.32 s14, .L35+4 .L4: vadd.f32 s0, s0, s14 vcmpe.f32 s0, s15 vmrs APSR_nzcv, FPSCR bmi .L4 .L2: vldr.32 s15, .L35+8 vcmpe.f32 s0, s15 vmrs APSR_nzcv, FPSCR ble .L5 vldr.32 s14, .L35+4 .L7: vsub.f32 s0, s0, s14 vcmpe.f32 s0, s15 vmrs APSR_nzcv, FPSCR bgt .L7 .L5: vmul.f32 s15, s0, s0 vldr.32 s14, .L35+12 vldr.32 s13, .L35+16 vmul.f32 s14, s15, s14 vcmpe.f32 s0, #0 vmov.f32 s15, s14 vmrs APSR_nzcv, FPSCR ite mi vfmami.f32 s15, s0, s13 vfnmspl.f32 s15, s0, s13 ; --- 第一次加载P(0.225)--- vldr.32 s13, .L35+20 vcmpe.f32 s15, #0 vmrs APSR_nzcv, FPSCR vmov.f32 s12, s15 ite mi vfnmami.f32 s12, s15, s15 vfnmspl.f32 s12, s15, s15 vfma.f32 s15, s12, s13 vldr.32 s13, .L35+24 vstr.32 s15, [r0] vcmpe.f32 s0, s13 vmrs APSR_nzcv, FPSCR bpl .L31 vldr.32 s13, .L35+28 vmov.f32 s15, #3.0e+0 vfma.f32 s15, s0, s13 vadd.f32 s15, s15, s14 .L16: vcmpe.f32 s15, #0 vmov.f32 s13, s15 vmrs APSR_nzcv, FPSCR ite mi vfnmami.f32 s13, s15, s15 vfnmspl.f32 s13, s15, s15 ; --- 第二次加载P(0.225)--- vldr.32 s14, .L35+20 vfma.f32 s15, s13, s14 vstr.32 s15, [r1] bx lr .L31: vldr.32 s15, .L35+32 vcmpe.f32 s0, s15 vmrs APSR_nzcv, FPSCR ble .L32 vldr.32 s13, .L35+28 vmov.f32 s15, #3.0e+0 vfms.f32 s15, s0, s13 vadd.f32 s15, s15, s14 b .L16 .L32: vmov.f32 s15, #1.0e+0 vsub.f32 s15, s15, s14 b .L16 .L35: .word -1068953637 .word 1086918619 .word 1078530011 .word 1053786491 .word 1067645315 .word 1046898278 .word -1077342245 .word 1076033923 .word 1070141403
原因分析与解决方案
为什么-O2下会重复加载?
GCC的-O2优化优先级是最大化执行速度,而非最小化内存访问次数。在这个函数中:
- 两次使用
P的代码路径被多个分支和内存操作(比如vstr.32 s15, [r0])隔开,寄存器分配器认为跨基本块保留寄存器的收益不如重新加载——因为Cortex-M4的FPU加载32位常量是单周期操作,开销极小,而保留寄存器可能会限制其他更关键变量的寄存器分配。 -Os是代码尺寸优先的优化,会尽量减少内存访问和重复指令,因此会选择把常量驻留在寄存器中。
解决方法
- 将宏改为静态常量:把
#define P 0.225f改为static const float P = 0.225f;,给GCC更明确的复用提示,让它识别到这个值会被多次使用。 - 抽成内联函数:把两次使用
P的近似计算逻辑抽成一个内联函数,让GCC更容易识别到可以复用寄存器中的常量。 - 手动绑定寄存器:使用GCC扩展
register float P_reg asm("s13") = 0.225f;,强制指定常量存储的寄存器,但这种方法会降低代码可移植性,且可能干扰寄存器分配器的其他优化。
内容的提问来源于stack exchange,提问作者VasiliSk
相关产品推荐
相关产品推荐

