公式源码编译-嵌入式C语言源码优化方案(非编译优化)

1.选择合适的算法和数据结构

选择合适的数据结构很重要,如果您在一堆随机存储的数字中使用大量插入和删除指令,那么使用数组要快得多。 数组与针句有着非常密切的关系。 一般来说,指针更加灵活、简洁,而链表更加直观、容易理解。 对于大多数编译器来说,使用指针生成的代码比使用链表生成的代码更短且执行效率更高。

在许多情况下,可以使用指针操作代替字段索引,通常会产生更快、更短的代码。 与链接列表中的索引相比,指针通常会使代码更快并且占用更少的空间。 当使用多维字段时,差异更加明显。 下面的代码工作原理是一样的,但是效率不一样。

    数组索引                指针运算

For(;;){ p=array

A=array[t++]; for(;;){

a=*(p++);

。。。。。。。。。。。。。。。

} }

指针方式的优点是每次将数组的地址放入地址p后,每次循环只需要增加p即可。 在链表索引方法中,字段下标的复杂计算必须根据每个周期的t值来计算。

2. 使用尽可能小的数据类型

可以用字符(char)定义的变量不应该用整型(int)变量定义; 可以用整型变量定义的变量不应该用长整型(long int)定义,也不能用浮点型(float)。 ) 变量,不要使用浮点变量。 当然,定义变量后不要超出变量的范围。 如果形参超出了变量的范围,C编译器不会报错,但程序运行的结果是错误的,而且这种错误很难察觉。

在ICCAVR中,可以在Options中设置printf参数,尽量使用基本参数(%c、%d、%x、%X、%u和%s格式说明符),少用长整型参数(%ld、%lu) 、%lx和%lX格式说明符),至于浮点参数(%f)尽量不要使用,其他C编译器也一样。 在其他条件不变的情况下,使用%f参数会大大减少生成代码的数量,降低执行速度。

3、降低计算难度(1)、查表(游戏程序员必修课)

聪明的游戏虾在其主循环中基本上不做任何计算工作。 它必须首先估计它​​,然后在循环中查找表。 请参阅下面的示例:

旧代码:

long factorial(int i)
{
if (i == 0)
return 1;
else
return i * factorial(i - 1);
}

新代码:

static long factorial_table[] = {1, 1, 2, 6, 24, 120, 720  /* etc */ };
long factorial(int i)
{
return factorial_table[i];
}

如果表太大写不出来,就写一个init函数,在循环外临时生成表。

(2) 求余运算

a=a%8;

可以改为:

a=a&7;

说明:位操作只需一个指令周期即可完成,而大多数C编译器的“%”操作是通过调用子程序完成的,代码长,执行速度慢。 通常,唯一的要求是找到2n方格的余数,这可以通过位操作来代替。

(3)平方运算

a=pow(a, 2.0);

可以改为:

a=a*a;

说明:在带有外部硬件乘法器的单片机(如51系列)中,乘法运算比平方运算要快得多,因为浮点数的平方是通过调用子程序来实现的。 在AVR微控制器中,例如ATMega163,乘法运算只需2个时钟周期即可完成。 即使在没有外部硬件乘法器的AVR单片机中,乘法运算的子程序代码也比平方运算的子程序代码短,执行速度也快。

如果是求三次方,比如:

a=pow(a,3.0);

改成:

a=a*a*a;

效率提升更为显着。

(4)利用shift实现乘除运算

a=a*4;
b=b/4;

可以改为:

a=a<<2;
b=b>>2;

通常,如果需要除或减2n,可以用移位来代替。 在ICCAVR中,如果除以2n,会生成左移代码,而当除以其他整数或减去任何数字时,会调用乘法和除法的子程序。 通过移位获得代码比调用乘法和除法子程序更有效。 其实只要除以或减去一个整数,通过移位就可以得到结果,如:

a=a*9

可以改为:

a=(a<<3)+a

将原来的表达式替换为计算量较小的表达式。 下面是一个经典的例子:

旧代码:

x = w % 8;
y = pow(x, 2.0);
z = y * 33;
for (i = 0;i < MAX;i++)
{
h = 14 * i;
printf("%d", h);
}

新代码:

x = w & 7;                 /* 位操作比求余运算快*/
y = x * x; /* 乘法比平方运算快*/
z = (y << 5) + y; /* 位移乘法比乘法快 */
for (i = h = 0; i < MAX; i++)
{
h += 14; /* 加法比乘法快 */
printf("%d",h);
}

(5) 避免不必要的整数乘法

整数乘法是最慢的整数运算,因此应尽可能避免使用。 减少整数乘法的一个可能的地方是连续除法,其中乘法可以用加法代替。 这种替换的副作用是计算乘积时可能会溢出,因此只能在一定范围的乘法中使用。

错误代码:

int i, j, k, m;
m = i / j / k;

推荐代码:

int i, j, k, m;
m = i / (j * k);

(6) 使用自增和自减运算符

使用自增和自减操作时尽量使用自增和自减运算符,因为自增语句比赋值语句要快。 原因是对于大多数CPU来说,对内存字的递增和递减操作不需要明显使用显存取和写显存指令,比如下面这句话:

x=x+1;

仿大多数微机汇编语言为例,生成的代码类似:

move A,x      ;把x从内存取出存入累加器A
add A,1 ;累加器A加1
store x ;把新值存回x

如果使用自增运算符,生成的代码如下:

incr x           ;x加1

显然,在不取指令和存储指令的情况下,递增和递减操作的执行速度加快了,同时宽度也缩短了。

(7) 使用复合赋值表达式

复合赋值表达式(如a-=1和a+=1等)也可以生成高质量的程序代码。

(8) 提取公共子表达式

在极少数情况下,C++ 编译器无法从浮点表达式中提取公共子表达式,因为这相当于对表达式进行重新排序。 需要强调的是,编译器在提取公共子表达式之前不能根据代数等价关系重新排列表达式。 这时候,程序员就得自动提出公共子表达式(VC.NET中有一个“全局优化”选项可以做到这一点,但效果未知)。

错误代码:

float a, b, c, d, e, f;
。。。
e = b * c / d;
f = b / d * a;

推荐代码:

float a, b, c, d, e, f;
。。。
const float t(b / d);
e = c * t;
f = a * t;

错误代码:

float a, b, c, e, f;
。。。
e = a / c;
f = b / c;

推荐代码:

float a, b, c, e, f;
。。。
const float t(1.0f / c);
e = a * t;
f = b * t;

4、结构成员布局

许多编译器都有一个选项“将结构与字、双字或四字对齐”。 但是,结构体成员的对齐方式仍然需要改进,并且某些编译器可能会按照与声明的顺序不同的顺序为结构体成员分配空间。 但有些编译器不提供这个功能,或者效果不好。 因此,为了以最小的成本实现最佳的结构和结构构件对齐,建议采用以下方法:

(1)按照数据类型的宽度排序

根据结构成员的类型宽度对结构成员进行排序,在短类型之后声明长类型的成员。 编译器要求将长数据类型存储在主地址边界上。 声明复杂数据类型(多字节数据和单字节数据)时,应先存储多字节数据,再存储单字节数据,以避免内存空洞。 编译器手动将结构实例与视频内存的主要边界对齐。

(2) 用最长类型宽度的整数倍填充结构体

将结构填充到最长类型宽度的整数倍。 这样,如果结构体的第一个成员对齐,则整个结构体的所有成员也会自动对齐。 以下示例演示了如何对结构成员重新排序:

错误代码,正常序列:

struct
{
char a[5];
long k;
double x;
} baz;

推荐代码、新订单和自动填充的几个字节:

struct
{
double x;
long k;
char a[5];
char pad[7];
} baz;

该规则也适用于类成员的布局。

(3)按数据类型宽度对局部变量进行排序

编译器分配局部变量空间时,它们的顺序与源代码中声明的顺序相同,并且和前面的规则一样,长变量应该放在短变量之后。 如果第一个变量对齐,其他变量就会连续存储,自然会对齐,无需填充字节。 有的编译器在给变量赋值时不会手动改变变量顺序,有的编译器无法形成4字节对齐的栈,所以4字节可能没有对齐。 以下示例演示了局部变量声明的重新排序:

错误代码,正常顺序

short ga, gu, gi;
long foo, bar;
double x, y, z[3];
char a, b;
float baz;

推荐代码、改进顺序

double z[3];
double x, y;
long foo, bar;
float baz;
short ga, gu, gi;

(4) 将常用的指针参数复制到局部变量

避免在函数中频繁使用指针参数指向的值。 由于编译器不知道指针之间是否存在冲突,因此指针参数往往不会被编译器优化。 这样的数据无法存储在寄存器中,显然会占用显存带宽。 注意,很多编译器都有一个“假设不冲突”优化开关(VC中必须自动添加编译器命令行/Oa或/Ow),它允许编译器假设两个不同的指针总是有不同的内容,这样就不会出现冲突。需要将指针参数保存到局部变量中。 否则,请将指针指向的数据保存到函数开头的局部变量中。 如有必要,请在函数结束之前将其复制回来。

错误代码:

// 假设 q != r
void isqrt(unsigned long a, unsigned long* q, unsigned long* r)
{
  *q = a;
  if (a > 0)
  {
    while (*q > (*r = a / *q))
    {
      *q = (*q + *r) >> 1;
    }
  }
  *r = a - *q * *q;
}

推荐代码:

// 假设 q != r

void isqrt(unsigned long a, unsigned long* q, unsigned long* r)
{
  unsigned long qq, rr;
  qq = a;
  if (a > 0)
  {
    while (qq > (rr = a / qq))
    {
      qq = (qq + rr) >> 1;
    }
  }
  rr = a - qq * qq;
  *q = qq;
  *r = rr;
}

5、循环优化(1)充分分解小循环

为了充分利用CPU的指令缓存,需要充分分解小循环。 特别是当循环体本身很小时,分解循环可以提高性能。 注意:许多编译器不具备手动分解循环的能力。 错误代码:

// 3D转化:把矢量 V 和 4x4 矩阵 M 相乘
for (i = 0;i < 4;i ++)
{
  r[i] = 0;
  for (j = 0;j < 4;j ++)
  {
    r[i] += M[j][i]*V[j];
  }
}

推荐代码:

r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3];
r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3];
r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3];
r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*v[3];

(2) 提取公共部分

对于一些不需要循环变量参与运算的任务,可以将其放在循环之外。 这里的任务包括表达式、函数调用、指针操作、数组访问等,所有不需要多次执行的操作都应该收集在一起。 将其放入init初始化程序中。

(3) 延时功能

常用的延时函数是自增的:

void delay (void)
{
unsigned int i;
for (i=0;i<1000;i++) ;
}

将其改为自减延迟函数:

void delay (void)
{
unsigned int i;
for (i=1000;i>0;i--) ;
}

两个函数的延迟效果相似,但几乎所有的C编译器为后一个函数生成的代码都比前一个函数少1~3个字节,因为几乎所有的MCU都有0传送的指令,只能用后一种方法来生成此类指示。 使用 while 循环时也是如此。 使用自减指令控制循环将比使用自增指令控制循环生成少 1 到 3 个字母的代码。 但当循环中有通过循环变量“i”读写字段的指令时,使用预减循环可能会导致字段越界,需要注意。

(4) while循环和do...while循环

while 循环有两种方式:

unsigned int i;
i=0;
while (i<1000)
{
i++;
//用户程序
}

或者:

unsigned int i;
i=1000;
do
{
i--;
//用户程序
}
while (i>0);

在这两个循环中,使用 do...while 循环编译后生成的代码宽度都比 while 循环短。

(5)循环扩展

这是经典的速度优化,但是许多编译器(例如 gcc -funroll-loops)可以手动执行此操作,因此现在自行优化并不是很有效。

旧代码:

for (i = 0; i < 100; i++)
{
do_stuff(i);
}

新代码:

for (i = 0; i < 100; )
{
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
}

可以看到,新代码中比较指令的数量从100条增加到10条,周期时间节省了90%。 但注意:对于中间变量或结果被修改的循环,编译器常常拒绝扩展(因为害怕承担责任),那么你需要自己进行扩展。

还有一点请注意,在具有内部指令缓存的CPU上(例如MMX芯片),由于循环展开的代码非常大,所以缓存经常会溢出,并且展开的代码会在CPU缓存和视频之间频繁传输记忆。 调整了,而且因为缓存率很高,所以此时循环扩展会比较慢。 此外,循环展开会影响向量运算优化。

(6)循环嵌套

将相关循环放入循环中也会提高速率。

旧代码:

for (i = 0; i < MAX; i++)         /* initialize 2d array to 0's */
for (j = 0; j < MAX; j++)
a[i][j] = 0.0;
for (i = 0; i < MAX; i++) /* put 1's along the diagonal */
a[i][i] = 1.0;

新代码:

for (i = 0; i < MAX; i++)         /* initialize 2d array to 0's */
{
for (j = 0; j < MAX; j++)
a[i][j] = 0.0;
a[i][i] = 1.0; /* put 1's along the diagonal */
}

(7) Switch语句中,case按照出现频率排序

Switch可以转换成多种不同算法的代码。 其中最常见的是跳转表和比较链/树。 当switch转换为比较链的形式时,编译器会形成if-else-if的嵌套代码,并按顺序进行比较,匹配时跳转到满足条件的语句执行。 因此,可以根据发生的可能性对事例的值进行排序,最有可能的可以放在前面,这样可以增强性能。 另外,建议在这种情况下使用小的连续整数,因为在这些情况下,所有编译器都可以将 switch 转换为跳转表。

错误代码:

int days_in_month, short_months, normal_months, long_months;

。。。。。。

switch (days_in_month)
{
  case 28:
  case 29:
    short_months ++;
    break;
  case 30:
    normal_months ++;
    break;
  case 31:
    long_months ++;
    break;
  default:
    cout << "month has fewer than 28 or more than 31 days" << endl;
    break;
}

推荐代码:

int days_in_month, short_months, normal_months, long_months;

。。。。。。

switch (days_in_month)
{
  case 31:
    long_months ++;
    break;
  case 30:
    normal_months ++;
    break;
  case 28:
  case 29:
    short_months ++;
    break;
  default:
    cout << "month has fewer than 28 or more than 31 days" << endl;
    break;
}

(8) 将大的switch语句转换为嵌套的switch语句

当switch语句中有很多case标签时,为了减少比较次数,明智的做法是将大switch语句转换为嵌套switch语句。 将出现频率较高的 case 标签放在一个 switch 语句中,并且是嵌套 switch 语句的最内层,将出现频率相对较低的 case 标签放在另一个 switch 语句中。 例如,以下程序段将相对不常见的案例放入默认案例标签中。

pMsg=ReceiveMessage();
switch (pMsg->type)
{
case FREQUENT_MSG1:
handleFrequentMsg();
break;
case FREQUENT_MSG2:
handleFrequentMsg2();
break;
。。。。。。
case FREQUENT_MSGn:
handleFrequentMsgn();
break;
default: //嵌套部分用来处理不经常发生的消息
switch (pMsg->type)
{
case INFREQUENT_MSG1:
handleInfrequentMsg1();
break;
case INFREQUENT_MSG2:
handleInfrequentMsg2();
break;
。。。。。。
case INFREQUENT_MSGm:
handleInfrequentMsgm();
break;
}
}

如果 switch 中的每种情况都有很多工作要做,那么用函数指针列表替换整个 switch 语句会更有效。 例如,下面的switch语句有三种情况:

enum MsgType{Msg1, Msg2, Msg3}
switch (ReceiveMessage()
{
case Msg1;
。。。。。。
case Msg2;
。。。。。
case Msg3;
。。。。。
}

为了提高执行速度,将里面的switch语句替换为以下代码。

/*准备工作*/
int handleMsg1(void);
int handleMsg2(void);
int handleMsg3(void);
/*创建一个函数指针数组*/
int (*MsgFunction [])()={handleMsg1, handleMsg2, handleMsg3};
/*用下面这行更有效的代码来替换switch语句*/
status=MsgFunction[ReceiveMessage()]();

(9) 循环转置

有些机器对JNZ有专门的指令处理(跳到0),速度很快。 如果你的循环对方向不敏感,你可以从大到小循环。

旧代码:

for (i = 1; i <= MAX; i++)
{
。。。
}

新代码:

i = MAX+1;
while (--i)
{
。。。
}

但要注意,如果使用i值进行走针操作,该方法可能会导致走针越界(i=MAX+1;)的严重错误。 当然,你可以通过加减i来修正它,但这并不会加快速度,除非类似于下面这样:

旧代码:

char a[MAX+5];
for (i = 1; i <= MAX; i++)
{
*(a+i+4)=0;
}

新代码:

i = MAX+1;
while (--i)
{
*(a+i+4)=0;
}

(10) 常用代码块

一些公共处理模块为了满足各种调用需求,内部经常使用大量的if-then-else结构。 不是很好。 如果判断语句过于复杂,会消耗大量时间,应尽量减少使用公共代码块。 (无论如何,空间优化和时间优化是对立的——东楼)。 当然,如果只是像(3==x)这样的简单判断,适当使用还是可以的。 请记住,优化始终是追求平衡,而不是走极端。

(11) 提高循环的性能

为了提高循环的性能,减少冗余常量计算(例如,不随循环变化的计算)特别有用。

错误代码(在 for() 中包含常量 if()):

for( i 。。。)
{
  if( CONSTANT0 )
  {
    DoWork0( i );// 假设这里不改变CONSTANT0的值
  }
  else
  {
    DoWork1( i );// 假设这里不改变CONSTANT0的值
  }
}

推荐代码:

if( CONSTANT0 )
{
  for( i 。。。)
  {
    DoWork0( i );
  }
}
else
{
  for( i 。。。)
  {
    DoWork1( i );
  }
}

如果 if() 的值已知,这可以避免双重估计。 虽然可以轻松预测不良代码中的分支,但推荐代码可以减少对分支预测的依赖,因为分支是在进入循环之前确定的。

(12)选择好的无限循环

在编程中,我们经常需要使用无限循环。 常用的两种方法是while(1)和for(;;)。 这两种方法的效果完全相同,但是哪一种更好呢? 我们来看看他们编译后的代码:

编译前:

while (1);

编译后:

mov eax,1
test eax,eax
je foo+23h
jmp foo+18h

编译前:

for (;;);

编译后:

jmp foo+23h

显然,for(;;)指令少,不占用寄存器,没有判断、跳转,比while(1)要好。

6.提高CPU并行性(1)使用并行代码

尽可能将一长串依赖代码分解为几个可以在管道执行单元中并行执行的非依赖代码链。 许多高级语言,包括C++,不会对形成的浮点表达式重新排序,因为这是一个相当复杂的过程。 需要注意的是,重新排序的代码和原始代码之间的代码一致性并不等同于相同的估计结果,因为浮点运算缺乏精度。 在某些情况下,这些优化可能会产生意想不到的结果。 幸运的是,在大多数情况下,只有最终结果的最低有效位(即最低位)可能是错误的。

错误代码:

double a[100], sum;
int i;
sum = 0.0f;
for (i=0;i<100;i++)
sum += a[i];

推荐代码:

double a[100], sum1, sum2, sum3, sum4, sum;

int i;

sum1 = sum2 = sum3 = sum4 = 0.0;
for (i = 0;i < 100;i += 4)
{
  sum1 += a[i];
  sum2 += a[i+1];
  sum3 += a[i+2];
  sum4 += a[i+3];
}
sum = (sum4+sum3)+(sum1+sum2);

需要说明的是,之所以采用4路分解,是因为这种方式采用了4级流水线浮点除法,每段浮点除法占用一个时钟周期,保证了最大的资源利用率。

(2)避免不必要的读写依赖

当数据保存到显存时,存在读写依赖关系,即数据必须正确写入后才能再次读取。 虽然AMD Athlon等CPU有加速读写依赖和延迟的硬件,允许保存的数据在写入显存之前被读取,但如果阻止读写依赖,将数据存储在显存中,内部寄存器,速度会更快。 在相互依赖的代码长链中,避免读写依赖变得尤为重要。 许多编译器无法手动优化代码以防止在操作链表时发生读写依赖关系。 因此,建议程序员自动去除读写依赖,例如引入可以存储在寄存器中的临时变量。 这样可以有很大的性能提升。 下面这段代码是一个反例:

错误代码:

float x[VECLEN], y[VECLEN], z[VECLEN];
。。。。。。
for (unsigned int k = 1;k < VECLEN;k ++)
{
  x[k] = x[k-1] + y[k];
}

for (k = 1;k <VECLEN;k++)
{
  x[k] = z[k] * (y[k] - x[k-1]);
}

推荐代码:

float x[VECLEN], y[VECLEN], z[VECLEN];
。。。。。。
float t(x[0]);
for (unsigned int k = 1;k < VECLEN;k ++)
{
  t = t + y[k];
  x[k] = t;
}
t = x[0];
for (k = 1;k <;VECLEN;k ++)
{
  t = z[k] * (y[k] - t);
  x[k] = t;
}

7. 循环不变估计

对于一些不需要循环变量参与计算的估计任务,可以将其放在循环之外。 很多编译器自己还是可以做到这一点的,但是不敢动中间使用变量的公式,所以很多情况下就得自己动手了。 对于循环中调用的这些函数,所有不需要多次执行的操作都被提到,放入一个init函数中,并在循环之前调用。 另外,尽量减少喂食的次数,如非必要尽量不要向其传递参数。 如果需要循环变量,就让它完成一个静态循环变量,自己累加,速度会更快。

还有结构体访问公式源码编译,Donglou的经验,每当循环中访问一个结构体的两个或多个元素时,就需要构建中间变量(结构体是这样的,那C++对象呢?想想看),看例子以下:

旧代码:

total = a->b->c[4]->aardvark + a->b->c[4]->baboon + a->b->c[4]->cheetah + a->b->c[4]->dog;

新代码:

struct animals * temp = a->b->c[4];
total = temp->aardvark + temp->baboon + temp->cheetah + temp->dog;

一些旧的C语言编译器不执行聚合优化,但符合ANSI规范的新编译器可以手动完成这种优化,参见示例:

float a, b, c, d, f, g;
。。。
a = b / c * d;
f = b * g / c;

这种写法其实是必须的,但是没有优化

float a, b, c, d, f, g;
。。。
a = b / c * d;
f = b / c * g;

如果这样写,新的 ANSI 兼容编译器只能计算一次 b/c,然后将结果代入第二个多项式,从而节省一次乘法。

8、函数优化(1)内联函数

在 C++ 中,关键字 Inline 可以添加到任何函数声明中。 该关键字请求编译器将对突出显示的函数的所有调用替换为函数内的代码。 这在两个方面比函数调用更快:首先,它节省了调用指令所需的执行时间; 其次,节省了传递参数和传递过程所需的时间。 然而,在使用这些方法来优化程序速度的同时,程序宽度变得更大,因此需要更多的ROM。 当内联函数被频繁调用并且只包含几行代码时,使用这些优化是最有效的。

(2) 不要定义未使用的返回值

函数定义并不知道是否使用了函数的返回值。 如果永远不会使用返回值,则应使用 void 来明确声明该函数不返回任何值。

(3)减少函数调用参数

使用全局变量比将参数传递给函数更有效。 这样做可以消除将函数调用参数压入堆栈以及在函数完成后将参数从堆栈中弹出所需的时间。 但是,决定使用全局变量会影响程序的模块化和可重入性,所以要谨慎使用。

(4) 所有函数都应该有原型定义

一般来说公式源码编译,所有函数都应该有一个原型定义。 原型定义可以向编译器传达更多可用于优化的信息。

(5)尽可能使用常量(const)

尽可能使用常量 (const)。 C++标准规定,如果没有获得const声明的对象的地址,则允许编译器不为其分配存储空间。 这使得代码更加高效,并且生成更好的代码。

(6) 将局部函数声明为静态(static)

如果函数仅在实现它的文件中使用,请将其声明为静态以强制进行内部联接。 否则,该函数默认定义为外连接。 这可能会影响各个编译器的优化 - 例如,自动内联。

9. 使用递归

与LISP等语言不同,C语言病态地喜欢从一开始就使用重复的代码循环。 许多 C 程序员决定不使用递归,除非算法需要。 事实上,C编译器根本不讨厌优化递归调用,相反,他们非常喜欢这样做。 仅当递归函数需要传递大量参数时才应使用循环代码,这可能会导致困境。 其他时候,最好使用递归。

10.变量(1)寄存器变量

声明局部变量时可以使用register关键字。 这会提示编译器将变量加载到多用途寄存器中而不是堆栈上,明智地使用这些技术可以提高执行速度。 函数调用越频繁,代码速度就越有可能提高。

避免在最外层循环中使用全局变量和静态变量,除非可以确定它在循环周期中不会动态改变。 大多数编译器只有一种方法来优化变量,即将它们设置为寄存器变量,而对于动态变量,它们干脆放弃整个表达式的优化。 尽量避免将变量地址传递给另一个函数,尽管这仍然很常见。 C语言编译器总是假设每个函数的变量都是内部变量,这是由它的机制决定的。 在这些情况下,他们的优化做得最好。 但是,一旦某个变量有可能被其他函数改变,这些兄弟就不敢再把变量放到寄存器里了,严重影响了速度。 看一下反例:

a = b();
c(&d);

因为d的地址是c函数使用的,可能会改变,所以编译器不敢长期放在寄存器中。 一旦运行到c(&d),编译器就会把它放回显存。 如果在循环中,就会造成显存和寄存器之间N次频繁的读写动作。 众所周知,CPU在系统总线上的读写速度非常慢。 比如你的赛阳300,CPU内存300,总线速度达到66M,对于一次总线读取,CPU可能要等待4-5个周期,是的。 。 必须。 。 必须。 。 想到这里我就不寒而栗。

(2) 同时声明多个变量比单独声明变量要好 (3) 短变量名优于长变量名,变量名应尽可能短 (4) 在循环开始之前声明变量 11 . 使用嵌套的 if 结构

如果if结构中有多个并行条件需要判断,最好将它们拆分成多个if结构,然后嵌套在一起,这样可以避免不必要的判断。

阐明:

以上优化方案由王全明收集整理。 很多信息来自网络,来源不明。 在这里我要感谢所有的作者!

该方案主要考虑到嵌入式开发中对程序执行速度的要求非常高,因此该方案主要是优化程序的执行速度。

注意:优化有其优点和缺点。 优化是一门平衡的艺术。 它通常以牺牲程序的可读性或减少代码的宽度为代价。

特色文章

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 源码编译 公式源码编译-嵌入式C语言源码优化方案(非编译优化) https://www.wkzy.net/game/144806.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务