嵌入式C代码的优化

嵌入式c代码的一些优化方法

  • 编译器优化选项
  1. -flto 链接时优化:编译时生成GIMPLE,而后链接时视同一个编译单元进行优化。使用此选项进行优化时,须要GCC插件,在生成库文件时须要指定。若是生成库时指定了该选项,则能够在链接该库时进行优化。
  2. -On 编译优化级别:有0,g,1,2,3,s,fast六个级别,0级不优化,fast最高(但有可能会出现问题),3和s可能须要实验选择一个能够接受结果。g优化级别能够用来调试,从而初步检查在更高级别优化时会出现的错误。
  3. -ffunction-sections -fdata-sections和链接时的选项--gc-sections配合能够去掉未使用的函数和数据,这个选项包含在了-On选项中。另外:-fdata-sections在avr-gcc编译器中若是使用变量属性io和address时会出现问题。
  4. 使用函数属性优化:例如__attribute__((flatten)),这个属性能够将函数内部的调用优化成内联函数;__attribute__((always_inline))能够将函数内联到其余函数中;__attribute__((optimize("Os")))能够指定函数的优化级别,这个级别能够和项目的优化选项不同,从而避免在特定级别优化时出现的错误。
  • 代码的优化
  1. 宏:使用宏并配合-On和-flto能够在编译时得到良好的优化结果,但也不必定,例如
// 使用宏进行优化时没有下面的代码好。
#define sprtbl ((uint8_t[][2]){{4, 2}, ..., {3, 128}})

// static uint8_t sprtbl[][2] = {
//     {4, 2}, {0, 4}, {5, 8}, {1, 16}, {6, 32}, {2, 64}, {3, 128},
// };

static inline uint8_t spr(uint32_t baud) {
    uint8_t m = F_CPU / baud;
    for (int i = 0; i < 7; i++) {
        if (m < sprtbl[i][1]) {
            return sprtbl[i][0];
        }
    };
    return 3;
}

2. 如同上面的代码,使用inline定义函数能够得到等同于宏的优化效果函数

3. 若是定义io指针结构,则使用const限定会得到较好的优化结果,例如,优化

typedef struct usart_io_s {
    volatile uint8_t* ubrrl;
    volatile uint8_t* ubrrh;
    ... // other io
} usart_io_t;

#define usart0_io { &UBRR0L, ... }
#define usart1_io { &UBRR1L, ... }

volatile struct usart_buffer_s {
    ...
} rx[USART_COUNT] = {}, tx[USART_COUNT] = {};

const struct usart_handle_s {
    usart_io_t                      io;
    volatile struct usart_buffer_s* rx;
    volatile struct usart_buffer_s* tx;
    ... // others fields
} usart[USART_COUNT] = {
    {usart0_io, &rx[0], &tx[0]},
    {usart1_io, &rx[1], &tx[1]},
}; // 当rx,tx使用指针时,usart[]结构能够得到优化。例如访问io成员时,能够将其优化为io指令。

4. 函数内使用const定义一些内部常量ui

[code A] 
void port_init(int id, int io) {
    const port_io_t p = pio[id / 8];
    if (io == 1) {
        *p.ddr |= ...;
    } else {
        *p.ddr &= ...;
    }
}
相比下面的代码,能够得到更多的优化
[code B]
void port_init(int id, int io) {
    if (io == 1) {
        *pio[id / 8].ddr |= ...;
    } else {
        *pio[id / 8].ddr &= ...;
    }
}
上面的代码中pio即便声明为const,A也会得到比B更多的优化(有前提条件,见后面的代码);
例如:
void main() {
  port_init(PA0, OUTPUT);
  ...
  port_init(PA7, OUTPUT);
}
若是你调用port_init少于3个(avr-gcc 5.4),则A与B没什么区别,若是大于3个,则A,B区别明显。(为何3个?忽然间以为gcc的编写者是我道门中人啊,三生万物啊)
​

5. 位域优化,位域能够简化对IO寄存器功能位的访问,一个赋值语句等同于读写改三个语句,固然带来的问题就是多任务环境下的数据同步问题。若是屡次访问同一(volatile)寄存器上的不一样位域,则会生成重复访问同一寄存器的汇编代码,下面给出一个方案,能够将代码优化成一次访问spa

将IO寄存器定义成联合体,以下:
    union sreg_t {
        uint8_t i8;
        struct {
            unsigned c : 1; //!< carry flag.
            unsigned z : 1; //!< zero flag.
            unsigned n : 1; //!< negative flag.
            unsigned v : 1; //!< two's complement overflow flag.
            unsigned s : 1; //!< sign bit.
            unsigned h : 1; //!< half carry flag.
            unsigned t : 1; //!< bit copy storage.
            unsigned i : 1; //!< global interrupt enable.
        };
    };
使用时,
sreg_t sreg = {};
sreg.c = 1;
sreg.i = 1;

// 真正的赋值,SREG为状态寄存器指针
SREG = sreg.i8;

以上方法适用于GCC插件