既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
指针链经常被用于访问结构数据。例如,常用的代码如下:
typedef struct { int x, y, z; } Point3;
typedef struct { Point3 *pos, *direction; } Object;
void InitPos1(Object *p)
{
p->pos->x = 0;
p->pos->y = 0;
p->pos->z = 0;
}
然而,这种的代码在每次操作时必须重复调用p->pos,因为编译器不知道p->pos->x与p->pos是相同的。一种更好的方法是缓存p->pos到一个局部变量:
void InitPos2(Object *p)
{
Point3 *pos = p->pos;
pos->x = 0;
pos->y = 0;
pos->z = 0;
}
另一种方法是在Object结构中直接包含Point3类型的数据,这能完全消除对Point3使用指针操作。
条件执行
条件执行语句大多在if语句中使用,也在使用关系运算符(<,==,>等)或者布尔值表达式(&&,!等)计算复杂表达式时使用。对于包含函数调用的代码片段,由于函数返回值会被销毁,因此条件执行是无效的。
因此,保持if和else语句尽可能简单是十分有益处的,因为这样编译器可以集中处理它们。关系表达式应该写在一起。
下面的例子展示编译器如何使用条件执行:
int g(int a, int b, int c, int d)
{
if (a > 0 && b > 0 && c < 0 && d < 0)
// grouped conditions tied up together//
return a + b + c + d;
return -1;
}
由于条件被聚集到一起,编译器能够将他们集中处理。
布尔表达式和范围检查
一个常用的布尔表达式是用于判断变量是否位于某个范围内,例如,检查一个图形坐标是否位于一个窗口内:
bool PointInRectangelArea (Point p, Rectangle *r)
{
return (p.x >= r->xmin && p.x < r->xmax &&
p.y >= r->ymin && p.y < r->ymax);
}
这里有一种更快的方法:x>min && x<max可以转换为(unsigned)(x-min)<(max-min)。这对于min等于0时更为有益。优化后的代码如下:
bool PointInRectangelArea (Point p, Rectangle *r)
{
return ((unsigned) (p.x - r->xmin) < r->xmax &&
(unsigned) (p.y - r->ymin) < r->ymax);
}
布尔表达式和零值比较
处理器的标志位在比较指令操作后被设置。标志位同样可以被诸如MOV、ADD、AND、MUL等基本算术和裸机指令改写。如果数据指令设置了标志位,N和Z标志位也将与结果与0比较一样进行设置。N标志表示结果是否是负值,Z标志表示结果是否是0。
C语言中,处理器中的N和Z标志位与下面的指令联系在一起:有符号关系运算x<0,x>=0,x0,x!=0;无符号关系运算x0,x!=0(或者x>0)。
C代码中每次关系运算符的调用,编译器都会发出一个比较指令。如果操作符是上面提到的,编译器便会优化掉比较指令。例如:
int aFunction(int x, int y)
{
if (x + y < 0)
return 1;
else
return 0;
}
尽可能的使用上面的判断方式,这可以在关键循环中减少比较指令的调用,进而减少代码体积并提高代码性能。C语言没有借位和溢出位的概念,因此,如果不借助汇编,不可能直接使用借位标志C和溢出位标志V。但编译器支持借位(无符号溢出),例如:
int sum(int x, int y)
{
int res;
res = x + y;
if ((unsigned) res < (unsigned) x) // carry set? //
res++;
return res;
}
懒检测开发
在if(a>10 && b=4)这样的语句中,确保AND表达式的第一部分最可能较快的给出结果(或者最早、最快计算),这样第二部分便有可能不需要执行。
用switch()函数替代if…else…
对于涉及if…else…else…这样的多条件判断,例如:
if( val == 1)
dostuff1();
else if (val == 2)
dostuff2();
else if (val == 3)
dostuff3();
使用switch可能更快:
switch( val )
{
case 1: dostuff1(); break;
case 2: dostuff2(); break;
case 3: dostuff3(); break;
}
在if()语句中,如果最后一条语句命中,之前的条件都需要被测试执行一次。Switch允许我们不做额外的测试。如果必须使用if…else…语句,将最可能执行的放在最前面。
二分中断
使用二分方式中断代码而不是让代码堆成一列,不要像下面这样做:
if(a==1) {
} else if(a==2) {
} else if(a==3) {
} else if(a==4) {
} else if(a==5) {
} else if(a==6) {
} else if(a==7) {
} else if(a==8)
{
}
使用下面的二分方式替代它,如下:
if(a<=4) {
if(a==1) {
} else if(a==2) {
} else if(a==3) {
} else if(a==4) {
}
}
else
{
if(a==5) {
} else if(a==6) {
} else if(a==7) {
} else if(a==8) {
}
}
或者如下:
if(a<=4)
{
if(a<=2)
{
if(a==1)
{
/* a is 1 */
}
else
{
/* a must be 2 */
}
}
else
{
if(a==3)
{
/* a is 3 */
}
else
{
/* a must be 4 */
}
}
}
else
{
if(a<=6)
{
if(a==5)
{
/* a is 5 */
}
else
{
/* a must be 6 */
}
}
else
{
if(a==7)
{
/* a is 7 */
}
else
{
/* a must be 8 */
}
}
}
比较如下两种case语句:
======001
switch语句vs查找表
Switch的应用场景如下:
- 调用一到多个函数
- 设置变量值或者返回一个值
- 执行一到多个代码片段
如果case标签很多,在switch的前两个使用场景中,使用查找表可以更高效的完成。例如下面的两种转换字符串的方式:
char * Condition_String1(int condition) {
switch(condition) {
case 0: return "EQ";
case 1: return "NE";
case 2: return "CS";
case 3: return "CC";
case 4: return "MI";
case 5: return "PL";
case 6: return "VS";
case 7: return "VC";
case 8: return "HI";
case 9: return "LS";
case 10: return "GE";
case 11: return "LT";
case 12: return "GT";
case 13: return "LE";
case 14: return "";
default: return 0;
}
}
char * Condition_String2(int condition) {
if ((unsigned) condition >= 15) return 0;
return
"EQ\0NE\0CS\0CC\0MI\0PL\0VS\0VC\0HI\0LS\0GE\0LT\0GT\0LE\0\0" +
3 * condition;
}
第一个程序需要240 bytes,而第二个仅仅需要72 bytes。
循环
循环是大多数程序中的常用的结构;程序执行的大部分时间发生在循环中,因此十分值得在循环执行时间上下一番功夫。
循环终止
如果不加注意,循环终止条件的编写会导致额外的负担。我们应该使用计数到零的循环和简单的循环终止条件。简单的终止条件消耗更少的时间。看下面计算n!的两个程序。第一个实现使用递增的循环,第二个实现使用递减循环。
int fact1_func (int n)
{
int i, fact = 1;
for (i = 1; i <= n; i++)
fact *= i;
return (fact);
}
int fact2_func(int n)
{
int i, fact = 1;
for (i = n; i != 0; i--)
fact *= i;
return (fact);
}
第二个程序的fact2_func执行效率高于第一个。
更快的for()循环
这是一个简单而高效的概念。通常,我们编写for循环代码如下:
for( i=0; i<10; i++){ ... }
i从0循环到9。如果我们不介意循环计数的顺序,我们可以这样写:
for( i=10; i--; ) { ... }
这样快的原因是因为它能更快的处理i的值–测试条件是:i是非零的吗?如果这样,递减i的值。对于上面的代码,处理器需要计算“计算i减去10,其值非负吗?如果非负,i递增并继续”。
简单的循环却有很大的不同。这样,i从9递减到0,这样的循环执行速度更快。
这里的语法有点奇怪,但确实合法的。循环中的第三条语句是可选的(无限循环可以写为for(;😉)。如下代码拥有同样的效果:
for(i=10; i; i--){}
或者更进一步的:
for(i=10; i!=0; i--){}
这里我们需要记住的是循环必须终止于0(因此,如果在50到80之间循环,这不会起作用),并且循环计数器是递减的。使用递增循环计数器的代码不享有这种优化。
合并循环
如果一个循环能解决问题坚决不用二个。但如果你需要在循环中做很多工作,这坑你并不适合处理器的指令缓存。这种情况下,两个分开的循环可能会比单个循环执行的更快。下面是一个例子:
======002
函数循环
调用函数时总是会有一定的性能消耗。不仅程序指针需要改变,而且使用的变量需要压栈并分配新变量。为提升程序的性能,在函数这点上有很多可以优化的。在保持程序代码可读性的同时也需要代码的大小是可控的。
如果在循环中一个函数经常被调用,那么就将循环纳入到函数中,这样可以减少重复的函数调用。代码如下:
for(i=0 ; i<100 ; i++)
{
func(t,i);
}
-
-
-
void func(int w,d)
{
lots of stuff.
}
应改为:
func(t);
-
-
-
void func(w)
{
for(i=0 ; i<100 ; i++)
{
//lots of stuff.
}
}
循环展开
简单的循环可以展开以获取更好的性能,但需要付出代码体积增加的代价。循环展开后,循环计数应该越来越小从而执行更少的代码分支。如果循环迭代次数只有几次,那么可以完全展开循环,以便消除循坏带来的负担。
这会带来很大的不同。循环展开可以带非常可观的节省性能,原因是代码不用每次循环需要检查和增加i的值。例如:
======003
编译器通常会像上面那样展开简单的,迭代次数固定的循环。但是像下面的代码:
for(i=0;i< limit;i++) { ... }
下面的代码(Example 1)明显比使用循环的方式写的更长,但却更有效率。block-sie的值设置为8仅仅适用于测试的目的,只要我们重复执行“loop-contents”相同的次数,都会有很好的效果。
在这个例子中,循环条件每8次迭代才会被检查,而不是每次都进行检查。由于不知道迭代的次数,一般不会被展开。因此,尽可能的展开循环可以让我们获得更好的执行速度。
//Example 1
#include<STDIO.H>
#define BLOCKSIZE (8)
void main(void)
{
int i = 0;
int limit = 33; /* could be anything */
int blocklimit;
/* The limit may not be divisible by BLOCKSIZE,
* go as near as we can first, then tidy up.
*/
blocklimit = (limit / BLOCKSIZE) * BLOCKSIZE;
/* unroll the loop in blocks of 8 */
while( i < blocklimit )
{
printf("process(%d)\n", i);
printf("process(%d)\n", i+1);
printf("process(%d)\n", i+2);
printf("process(%d)\n", i+3);
printf("process(%d)\n", i+4);
printf("process(%d)\n", i+5);
printf("process(%d)\n", i+6);
printf("process(%d)\n", i+7);
/* update the counter */
i += 8;
}
/*
* There may be some left to do.
* This could be done as a simple for() loop,
* but a switch is faster (and more interesting)
*/
if( i < limit )
{
/* Jump into the case at the place that will allow
* us to finish off the appropriate number of items.
*/
switch( limit - i )
{
case 7 : printf("process(%d)\n", i); i++;
case 6 : printf("process(%d)\n", i); i++;
case 5 : printf("process(%d)\n", i); i++;
case 4 : printf("process(%d)\n", i); i++;
case 3 : printf("process(%d)\n", i); i++;
case 2 : printf("process(%d)\n", i); i++;
case 1 : printf("process(%d)\n", i);
}
}
}
统计非零位的数量
通过不断的左移,提取并统计最低位,示例程序1高效的检查一个数组中有几个非零位。示例程序2被循环展开四次,然后通过将四次移位合并成一次来优化代码。经常展开循环,可以提供很多优化的机会。
//Example - 1
int countbit1(uint n)
{
int bits = 0;
while (n != 0)
{
if (n & 1) bits++;
n >>= 1;
}
return bits;
}
//Example - 2
int countbit2(uint n)
{
int bits = 0;
while (n != 0)
{
if (n & 1) bits++;
if (n & 2) bits++;
if (n & 4) bits++;
if (n & 8) bits++;
n >>= 4;
}
return bits;
}
尽早的断开循环
通常,循环并不需要全部都执行。例如,如果我们在从数组中查找一个特殊的值,一经找到,我们应该尽可能早的断开循环。例如:如下循环从10000个整数中查找是否存在-99。
found = FALSE;
for(i=0;i<10000;i++)
{
if( list[i] == -99 )
{
found = TRUE;
}
}
if( found )
printf("Yes, there is a -99. Hooray!\n");
上面的代码可以正常工作,但是需要循环全部执行完毕,而不论是否我们已经查找到。更好的方法是一旦找到我们查找的数字就终止继续查询。
found = FALSE;
for(i=0; i<10000; i++)
{
if( list[i] == -99 )
{
found = TRUE;
break;
}
}
if( found )
printf("Yes, there is a -99. Hooray!\n");
假如待查数据位于第23个位置上,程序便会执行23次,从而节省9977次循环。
函数设计
设计小而简单的函数是个很好的习惯。这允许寄存器可以执行一些诸如寄存器变量申请的优化,是非常高效的。
函数调用的性能消耗
函数调用对于处理器的性能消耗是很小的,只占有函数执行工作中性能消耗的一小部分。参数传入函数变量寄存器中有一定的限制。这些参数必须是整型兼容的(char,shorts,ints和floats都占用一个字)或者小于四个字大小(包括占用2个字的doubles和long longs)。
如果参数限制个数为4,那么第五个和之后的字就会存储在栈上。这便在调用函数是需要从栈上加载参数从而增加存储和读取的消耗。
看下面的代码:
int f1(int a, int b, int c, int d) {
return a + b + c + d;
}
int g1(void) {
return f1(1, 2, 3, 4);
}
int f2(int a, int b, int c, int d, int e, int f) {
return a + b + c + d + e + f;
}
ing g2(void) {
![img](https://img-blog.csdnimg.cn/img_convert/4e257f973290852286fab59d698847b4.png)
![img](https://img-blog.csdnimg.cn/img_convert/5a51254fbf0054078b62291a72a62693.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
是整型兼容的(char,shorts,ints和floats都占用一个字)或者小于四个字大小(包括占用2个字的doubles和long longs)。
如果参数限制个数为4,那么第五个和之后的字就会存储在栈上。这便在调用函数是需要从栈上加载参数从而增加存储和读取的消耗。
看下面的代码:
int f1(int a, int b, int c, int d) {
return a + b + c + d;
}
int g1(void) {
return f1(1, 2, 3, 4);
}
int f2(int a, int b, int c, int d, int e, int f) {
return a + b + c + d + e + f;
}
ing g2(void) {
[外链图片转存中…(img-DIhzP5la-1715664601852)]
[外链图片转存中…(img-G7gy9Rag-1715664601853)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新