You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开启-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优化优先级是最大化执行速度,而非最小化内存访问次数。在这个函数中:

  1. 两次使用P的代码路径被多个分支和内存操作(比如vstr.32 s15, [r0])隔开,寄存器分配器认为跨基本块保留寄存器的收益不如重新加载——因为Cortex-M4的FPU加载32位常量是单周期操作,开销极小,而保留寄存器可能会限制其他更关键变量的寄存器分配。
  2. -Os是代码尺寸优先的优化,会尽量减少内存访问和重复指令,因此会选择把常量驻留在寄存器中。

解决方法

  1. 将宏改为静态常量:把#define P 0.225f改为static const float P = 0.225f;,给GCC更明确的复用提示,让它识别到这个值会被多次使用。
  2. 抽成内联函数:把两次使用P的近似计算逻辑抽成一个内联函数,让GCC更容易识别到可以复用寄存器中的常量。
  3. 手动绑定寄存器:使用GCC扩展register float P_reg asm("s13") = 0.225f;,强制指定常量存储的寄存器,但这种方法会降低代码可移植性,且可能干扰寄存器分配器的其他优化。

内容的提问来源于stack exchange,提问作者VasiliSk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 13:14:51