首页 > 解决方案 > 为小尺寸优化 C 代码 - 共享静态变量?

问题描述

我有两个功能,都与此类似:

void Bit_Delay()
{
    //this is a tuned tight loop for 8 MHz to generate timings for 9600 baud
    volatile char z = 12;
    
    while(z)
    {
        z++;
        z++;
        z++;
        z++;
        z -= 5;
    }
}

(第二个函数是类似的,而是使用 18 而不是 12 作为计数器)。

代码按原样完美运行(z 在内部出现在每个函数的本地),但我正试图在我的可执行文件中塞进更多的功能,然后才能使用(可怕的)有限的可用闪存。

我的想法是将z变量提升为全局变量(可变静态)。因为这两个函数是有效的原子操作(它是一个单线程 CPU,没有中断可以干扰),我认为这两个函数可以共享单个变量,从而节省一点堆栈操作。

这没有用。很明显,编译器正在优化与z完全相关的大部分代码!然后代码无法正常运行(运行速度太快),编译后的二进制文件的大小下降到大约 50% 左右。

我意识到我需要将 z 变量标记为 volatile 以防止编译器删除它所知道的代码,它每次都在计算一个固定的(因此可以简化为一个常量)数字。

问题:

我可以进一步优化它,并欺骗编译器保持这两个函数不变吗?我正在使用“-Os”(针对小型二进制文件进行优化)进行编译。

这是为那些在家玩的人逐字逐句的整个程序...

#include <avr/io.h>

#define RX_PIN (1 << PORTB0) //physical pin 3
#define TX_PIN (1 << PORTB1) //physical pin 1

void Bit_Delay()
{
    //this is a tuned tight loop for 8 MHz to generate timings for 9600 baud
    volatile char z = 12;
    
    while(z)
    {
        z++;
        z++;
        z++;
        z++;
        z -= 5;
    }
}

void Serial_TX_Char(char c)
{
    char i;
    
    //start bit
    PORTB &= ~TX_PIN;
    Bit_Delay();
    
    for(i = 0 ; i < 8 ; i++)
    {
        //output the data bits, LSB first
        if(c & 0x01)
            PORTB |= TX_PIN;
        else
            PORTB &= ~TX_PIN;
        
        c >>= 1;
        Bit_Delay();
    }

    //stop bit  
    PORTB |= TX_PIN;
    Bit_Delay();
}

char Serial_RX_Char()
{
    char retval = 0;
    volatile char z = 18; //1.5 bits delay

    //wait for idle high
    while((PINB & RX_PIN) == 0)
    {}
    
    //wait for start bit falling-edge
    while((PINB & RX_PIN) != 0)
    {}

    //1.5 bits delay
    while(z)
    {
        z++;
        z++;
        z++;
        z++;
        z -= 5;
    }

    for(z = 0 ; z < 8 ; z++)
    {
        retval >>= 1; //make space for the new bit
        retval |= (PINB & RX_PIN) << (8 - RX_PIN); //get the bit and store it
        Bit_Delay();
    }
    
    return retval;      
}

int main(void)
{
    CCP = 0xd8; //protection signature for clock registers (see datasheet)
    CLKPSR = 0x00; //set the clock prescaler to "div by 1"
    DDRB |= TX_PIN;
    PORTB |= TX_PIN; //idle high
        
    while (1) 
        Serial_TX_Char(Serial_RX_Char() ^ 0x20);
}

目标 CPU 是一个Atmel ATTiny5微控制器,上面的代码占用了 94.1% 的 FLASH 内存!如果您使用 9600 波特,8N1 的串行端口连接到芯片,您可以输入字符并返回它们,并将位 0x20 翻转(大写到小写,反之亦然)。

当然,这不是一个严肃的项目,我只是在尝试看看我可以在这个芯片中塞进多少功能。我不会费心在汇编中重写它,我严重怀疑我能比 GCC 的优化器做得更好!

编辑

@Frank 询问了我正在使用的 IDE / 编译器...

微芯片工作室 (7.0.2542)

传递给编译器的“所有选项”字符串avr-gcc...

-x c -funsigned-char -funsigned-bitfields -DDEBUG  -I"C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.8.332\include"  -Os -ffunction-sections -fdata-sections -fpack-struct -fshort-enums -g2 -Wall -mmcu=attiny5 -B "C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.8.332\gcc\dev\attiny5" -c -std=gnu99 -MD -MP -MF "$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -MT"$(@:%.o=%.o)" 

标签: coptimizationmicrocontrolleratmelstudioattiny

解决方案


我质疑以下假设:

这没有用。很明显,编译器正在完全优化大部分与 z 相关的代码!然后代码无法正常运行(运行速度太快),编译后的二进制文件的大小下降到大约 50% 左右。

查看https://gcc.godbolt.org/z/sKdz3h8oP,似乎实际上正在执行循环,但是,无论出于何种原因 each z++,当使用全局 volatilez时,来自:

subi r28,lo8(-(1))
sbci r29,hi8(-(1))
ld r20,Y
subi r28,lo8((1))
sbci r29,hi8((1))
subi r20,lo8(-(1))
subi r28,lo8(-(1))
sbci r29,hi8(-(1))
st Y,r20
subi r28,lo8((1))
sbci r29,hi8((1))

至:

lds r20,z
subi r20,lo8(-(1))
sts z,r20

您将需要重新校准 12、18 和 5 常量以使波特率正确(因为在每个循环中执行的指令较少),但编译版本中存在逻辑。

需要明确的是:这对我来说看起来很奇怪,本地 volatile 版本显然没有被正确编译。我确实发现了一个旧的 gcc 错误:https ://gcc.gnu.org/bugzilla/show_bug.cgi?id=33970 ,但它似乎没有涵盖局部变量的情况。


推荐阅读