良许Linux教程网 干货合集 嵌入式代码的一些常见操作

嵌入式代码的一些常见操作

嵌入式代码优化是一项错综复杂的工程,其成败不仅仅取决于代码本身的设计,还受到目标硬件平台、编译器的特性以及优化目标(如速度、内存利用、功耗等)的影响。

然而,在编写嵌入式代码时,存在一些普适的技巧值得考虑:

利用查表法

在内存资源相对充足的情况下,有时可以通过牺牲一些存储空间来换取程序的运行速度。查表法典型地展示了以空间换取时间的思想。

举例来说:假设需要编写一个程序来统计4位二进制数据中1的个数。

利用查表法可以这样实现:

static int table[16] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
int get_digits_1_num(unsigned char data)
{
 int cnt = 0;
 unsigned char temp = data & 0xf;  
 
 cnt = table[temp];
 
 return cnt;
}

优于:

int get_digits_1_num(unsigned char data)
{
 int cnt = 0;
 unsigned char temp = data & 0xf;  
 
 for (int i = 0; i if (temp & 0x01)
  {
   cnt++;
  }
  temp >>= 1;
 }
 
 return cnt;
}

查表法把0x0~0xF中的所有数据中每个数据的1的个数都记录下来,存放到一个表中。这样一来,数据数据中1的个数就建立起了一一对应关系,就可以通过数组索引来获取得到结果。常规法使用for循环的方式来实现,缺点是占用了不少处理器的时间。

特别地,对于越复杂地运算,查表法较常规法更有优势。另一方面,查表法的代码往往比常规法要简洁些。

使用柔性数组

 

C99中,结构体中的最后一个元素允许是未知大小的数组,这就叫作 柔性数组

image-20240328224651941
image-20240328224651941

柔性数组的特点:

  • 结构体中柔性数组成员前面必须至少有一个其他成员。
  • sizeof返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc()函数进行内存的动态分配。

在C99标准环境中,使用柔性数组:

typedef struct _protocol_format
{
    uint16_t head;    
    uint8_t id;
    uint8_t type;
    uint8_t length;
    uint8_t value[];
}protocol_format_t;

优于使用指针:

typedef struct _protocol_format
{
    uint16_t head;    
    uint8_t id;
    uint8_t type;
    uint8_t length;
    uint8_t *value;
}protocol_format_t;
  • 柔性数组的方式结构体占用较指针的方式少。
  • 柔性数组的方式相对与指针的方式更为简洁,给结构体申请空间的同时也给柔性数组申请空间,柔性数组的方式只需要申请一次空间,是一块连续内存,连续的内存有益于提高访问速度;而指针的方式,除了给结构体申请空间之外,还得给结构体里的指针成员申请空间。
  • 使用指针的方式写代码会比柔性数组的方式会繁琐一些,特别地,如果在释放内存的时候把顺序弄反了,则结构体里的指针成员所指向的内存就释放不掉,会造成内存泄露。

使用位操作

1、使用位域

有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。

image-20240328224731369
image-20240328224731369

比如:管理一些标志位。

使用位域:

struct {
    unsigned char flag1:1;
    unsigned char flag2:1;
    unsigned char flag3:1;
    unsigned char flag4:1;
    unsigned char flag5:1;
    unsigned char flag6:1;
    unsigned char flag7:1;
    unsigned char flag8:1;
} flags;

优于:

struct {
    unsigned char flag1;
    unsigned char flag2;
    unsigned char flag3;
    unsigned char flag4;
    unsigned char flag5;
    unsigned char flag6;
    unsigned char flag7;
    unsigned char flag8;
} flags;

2、使用位操作代替除法和乘法

使用位操作:

uint32_t val = 1024;
uint32_t doubled = val > 1; 

优于:

uint32_t val = 1024;
uint32_t doubled = val * 2
uint32_t halved = val / 2

循环展开

有时候,可以牺牲一点代码的简洁度、减少循环控制语句的执行频率以提高性能。

无依赖的循环展开:

process(array[0]);
process(array[1]);
process(array[2]);
process(array[3]);

优于:

for (int i = 0; i 

有依赖的循环展开:

long calc_sum(int *a, int *b)
{
 long sum0 = 0;
 long sum1 = 0;
 long sum2 = 0;
 long sum3 = 0;
 
 for (int i = 0; i return (sum0 + sum1 + sum2 + sum3);
}

优于:

long calc_sum(int *a, int *b)
{
 long sum = 0;
 
 for (int i = 0; i return sum;
}

尽可能把长的有依赖的代码链分解成几个可以在流水线执行单元中并行执行的没有依赖的代码链,提高流水线的连续性。通常4次展开为最佳方式。

使用内联函数

使用内联函数替换重复的短代码,一方面,可以避免函数的回调,加速了程序的执行,利用指令缓存,增强局部访问性;另一方面,可以方便代码管理。

如:翻转led的操作。

static inline void toggle_led(uint8_t pin)
{
    PORT ^= 1 

使用合适的数据类型

首先使用合适的数据类型。

比如几种数据类型都满足需求的情况下,更小的可能并不是最合适的。

比如:素组索引的变量类型。

数组索引应尽量采用int类型。

int i;
for (i = 0; i 

优于:

char i;
for (i = 0; i 

定义为char类型,一般会有溢出的风险,因此编译器需要使用多余的指令判断是否溢出;而使用int类型,一般编译器默认不会超过这么大的循环次数,从而减少了不必要的指令。

其它情况下,在满足数据范围的情况下,能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用整型变量定义的变量就不要用长整型(long int),能不使用浮点型(float)变量就不要使用浮点型变量。

多重循环优化

长循环在最内层:

for (col = 0; col for (row = 0; row 

优于长循环在最外层:

for (row = 0; row for(col=0; col 

在多重循环中,应当将最长的循环放在最内层, 最短的循环放在最外层,以减少 CPU 跨切循环层的次数。

尽早退出循环

通常,循环并不需要全部都执行。

例如,如果我们在从数组中查找一个特殊的值,一经找到,我们应该尽可能早的断开循环。例如:如下循环从10000个整数中查找是否存在-99。

char found = FALSE;
for(i = 0; i if (list[i] == -99)
    {
        found = TRUE;
    }
}
 
if (found) 
{
    printf("Yes, there is a -99. Hooray!\n");
}

这段代码无论我们是否查找得到,循环都会全部执行完。更好的方法是一旦找到我们查找的数字就终止继续查询。把程序修改为:

found = FALSE;
for (i = 0; i if (list[i] == -99)
    {
        found = TRUE;
        break;
    }
}
 
if (found) 
{
    printf("Yes, there is a -99. Hooray!\n");
}

假如待查数据位于第23个位置上,程序便会执行23次,从而节省9977次循环。

结构体内存对齐

必要时,手动对齐结构体的内存排列。

比如:

typedef struct test_struct
{
 char a;  
 short b;     
 char c;     
 int d;
 char e;
}test_struct;

该结构体在32bit环境中,该结构体所占的字节数为16。

可以手动调整各成员的位置来进行空白字节填充以达到对齐的效果。如:

typedef struct test_struct
{
 char a;  
 char c; 
 short b;         
 int d;
 char e;
}test_struct;

则结构体变量test_s所占的字节数变为12字节,比原来的16字节省下了4个字节。

优化中断处理

确保中断处理快速且尽可能短。

// 中断例程应该尽量简短
void ISR() 
{
    flag = true;
}

利用硬件特性

使用硬件模块或特有指令来减轻CPU负担。

// 比如,直接使用DMA传输而不经由CPU
DMA_Config(&src, &dest, length);
DMA_Start();

以上就是本次的分享。一些优化可能会增加代码的复杂性或降低可读性或其它方面的影响,因此在决定应用优化时,需权衡不同方面的影响。

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部