99精品伊人亚洲|最近国产中文炮友|九草在线视频支援|AV网站大全最新|美女黄片免费观看|国产精品资源视频|精彩无码视频一区|91大神在线后入|伊人终合在线播放|久草综合久久中文

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

C語言代碼優(yōu)化11種實用方法

Q4MP_gh_c472c21 ? 來源:嵌入式云IOT技術(shù)圈 ? 作者:王全明 ? 2022-04-08 09:31 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

1、選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)

選擇一種合適的數(shù)據(jù)結(jié)構(gòu)很重要,如果在一堆隨機存放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。數(shù)組與指針語句具有十分密切的關(guān)系,一般來說,指針比較靈活簡潔,而數(shù)組則比較直觀,容易理解。對于大部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。

在許多種情況下,可以用指針運算代替數(shù)組索引,這樣做常常能產(chǎn)生又快又短的代碼。與數(shù)組索引相比,指針一般能使代碼速度更快,占用空間更少。使用多維數(shù)組時差異更明顯。下面的代碼作用是相同的,但是效率不一樣。

    數(shù)組索引                指針運算

    For(;;){               p=array

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

                                a=*(p++);

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

    }                      }

指針方法的優(yōu)點是,array的地址每次裝入地址p后,在每次循環(huán)中只需對p增量操作。在數(shù)組索引方法中,每次循環(huán)中都必須根據(jù)t值求數(shù)組下標的復(fù)雜運算。

2、使用盡量小的數(shù)據(jù)類型

能夠使用字符型(char)定義的變量,就不要使用整型(int)變量來定義;能夠使用整型變量定義的變量就不要用長整型(long int),能不使用浮點型(float)變量就不要使用浮點型變量。當(dāng)然,在定義變量后不要超過變量的作用范圍,如果超過變量的范圍賦值,C編譯器并不報錯,但程序運行結(jié)果卻錯了,而且這樣的錯誤很難發(fā)現(xiàn)。

在ICCAVR中,可以在Options中設(shè)定使用printf參數(shù),盡量使用基本型參數(shù)(%c、%d、%x、%X、%u和%s格式說明符),少用長整型參數(shù)(%ld、%lu、%lx和%lX格式說明符),至于浮點型的參數(shù)(%f)則盡量不要使用,其它C編譯器也一樣。在其它條件不變的情況下,使用%f參數(shù),會使生成的代碼的數(shù)量增加很多,執(zhí)行速度降低。

3、減少運算的強度

(1)、查表(游戲程序員必修課)

一個聰明的游戲大蝦,基本上不會在自己的主循環(huán)里搞什么運算工作,絕對是先計算好了,再到循環(huán)里查表。看下面的例子:

舊代碼:

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函數(shù),在循環(huán)外臨時生成表格。

(2)求余運算

a=a%8;

可以改為:

a=a&7;

說明:位操作只需一個指令周期即可完成,而大部分的C編譯器的“%”運算均是調(diào)用子程序來完成,代碼長、執(zhí)行速度慢。通常,只要求是求2n方的余數(shù),均可使用位操作的方法來代替。

(3)平方運算

a=pow(a, 2.0);

可以改為:

a=a*a;

說明:在有內(nèi)置硬件乘法器的單片機中(如51系列),乘法運算比求平方運算快得多,因為浮點數(shù)的求平方是通過調(diào)用子程序來實現(xiàn)的,在自帶硬件乘法器的AVR單片機中,如ATMega163中,乘法運算只需2個時鐘周期就可以完成。既使是在沒有內(nèi)置硬件乘法器的AVR單片機中,乘法運算的子程序比平方運算的子程序代碼短,執(zhí)行速度快。

如果是求3次方,如:

a=pow(a,3.0);

更改為:

a=a*a*a;

則效率的改善更明顯。

(4)用移位實現(xiàn)乘除法運算

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

可以改為:

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

通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果乘以2n,都可以生成左移的代碼,而乘以其它的整數(shù)或除以任何數(shù),均調(diào)用乘除法子程序。用移位的方法得到代碼比調(diào)用乘除法子程序生成的代碼效率高。實際上,只要是乘以或除以一個整數(shù),均可以用移位的方法得到結(jié)果,如:

a=a*9

可以改為:

a=(a<<3)+a

采用運算量更小的表達式替換原來的表達式,下面是一個經(jīng)典例子:

舊代碼:

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)避免不必要的整數(shù)除法

整數(shù)除法是整數(shù)運算中最慢的,所以應(yīng)該盡可能避免。一種可能減少整數(shù)除法的地方是連除,這里除法可以由乘法代替。這個替換的副作用是有可能在算乘積時會溢出,所以只能在一定范圍的除法中使用。

不好的代碼:

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

推薦的代碼:

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

(6)使用增量和減量操作符

在使用到加一和減一操作時盡量使用增量和減量操作符,因為增量符語句比賦值語句更快,原因在于對大多數(shù)CPU來說,對內(nèi)存字的增、減量操作不必明顯地使用取內(nèi)存和寫內(nèi)存的指令,比如下面這條語句:

x=x+1;

模仿大多數(shù)微機匯編語言為例,產(chǎn)生的代碼類似于:

move A,x      ;把x從內(nèi)存取出存入累加器A
add A,1       ;累加器A加1
store x        ;把新值存回x

如果使用增量操作符,生成的代碼如下:

incr x           ;x加1

顯然,不用取指令和存指令,增、減量操作執(zhí)行的速度加快,同時長度也縮短了。

(7)使用復(fù)合賦值表達式

復(fù)合賦值表達式(如a-=1及a+=1等)都能夠生成高質(zhì)量的程序代碼。

(8)提取公共的子表達式

在某些情況下,C++編譯器不能從浮點表達式中提出公共的子表達式,因為這意味著相當(dāng)于對表達式重新排序。

需要特別指出的是,編譯器在提取公共子表達式前不能按照代數(shù)的等價關(guān)系重新安排表達式。這時,程序員要手動地提出公共的子表達式(在VC.NET里有一項“全局優(yōu)化”選項可以完成此工作,但效果就不得而知了)。

不好的代碼:

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、結(jié)構(gòu)體成員的布局

很多編譯器有“使結(jié)構(gòu)體字,雙字或四字對齊”的選項。但是,還是需要改善結(jié)構(gòu)體成員的對齊,有些編譯器可能分配給結(jié)構(gòu)體成員空間的順序與他們聲明的不同。但是,有些編譯器并不提供這些功能,或者效果不好。

所以,要在付出最少代價的情況下實現(xiàn)最好的結(jié)構(gòu)體和結(jié)構(gòu)體成員對齊,建議采取下列方法:

(1)按數(shù)據(jù)類型的長度排序

把結(jié)構(gòu)體的成員按照它們的類型長度排序,聲明成員時把長的類型放在短的前面。編譯器要求把長型數(shù)據(jù)類型存放在偶數(shù)地址邊界。

在申明一個復(fù)雜的數(shù)據(jù)類型 (既有多字節(jié)數(shù)據(jù)又有單字節(jié)數(shù)據(jù)) 時,應(yīng)該首先存放多字節(jié)數(shù)據(jù),然后再存放單字節(jié)數(shù)據(jù),這樣可以避免內(nèi)存的空洞。編譯器自動地把結(jié)構(gòu)的實例對齊在內(nèi)存的偶數(shù)邊界。

(2)把結(jié)構(gòu)體填充成最長類型長度的整倍數(shù)

把結(jié)構(gòu)體填充成最長類型長度的整倍數(shù)。照這樣,如果結(jié)構(gòu)體的第一個成員對齊了,所有整個結(jié)構(gòu)體自然也就對齊了。下面的例子演示了如何對結(jié)構(gòu)體成員進行重新排序:

不好的代碼,普通順序:

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

推薦的代碼,新的順序并手動填充了幾個字節(jié):

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

這個規(guī)則同樣適用于類的成員的布局。

(3)按數(shù)據(jù)類型的長度排序本地變量

當(dāng)編譯器分配給本地變量空間時,它們的順序和它們在源代碼中聲明的順序一樣,和上一條規(guī)則一樣,應(yīng)該把長的變量放在短的變量前面。如果第一個變量對齊了,其它變量就會連續(xù)的存放,而且不用填充字節(jié)自然就會對齊。有些編譯器在分配變量時不會自動改變變量順序,有些編譯器不能產(chǎn)生4字節(jié)對齊的棧,所以4字節(jié)可能不對齊。

下面這個例子演示了本地變量聲明的重新排序:

不好的代碼,普通順序

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)把頻繁使用的指針型參數(shù)拷貝到本地變量

避免在函數(shù)中頻繁使用指針型參數(shù)指向的值。因為編譯器不知道指針之間是否存在沖突,所以指針型參數(shù)往往不能被編譯器優(yōu)化。這樣數(shù)據(jù)不能被存放在寄存器中,而且明顯地占用了內(nèi)存帶寬。

注意,很多編譯器有“假設(shè)不沖突”優(yōu)化開關(guān)(在VC里必須手動添加編譯器命令行/Oa或/Ow),這允許編譯器假設(shè)兩個不同的指針總是有不同的內(nèi)容,這樣就不用把指針型參數(shù)保存到本地變量。否則,請在函數(shù)一開始把指針指向的數(shù)據(jù)保存到本地變量。如果需要的話,在函數(shù)結(jié)束前拷貝回去。

不好的代碼:

// 假設(shè) 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;
}

推薦的代碼:

// 假設(shè) 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、循環(huán)優(yōu)化

(1)充分分解小的循環(huán)

要充分利用CPU的指令緩存,就要充分分解小的循環(huán)。特別是當(dāng)循環(huán)體本身很小的時候,分解循環(huán)可以提高性能。

注意,很多編譯器并不能自動分解循環(huán)。不好的代碼:

// 3D轉(zhuǎn)化:把矢量 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)提取公共部分

對于一些不需要循環(huán)變量參加運算的任務(wù)可以把它們放到循環(huán)外面,這里的任務(wù)包括表達式、函數(shù)的調(diào)用、指針運算、數(shù)組訪問等,應(yīng)該將沒有必要執(zhí)行多次的操作全部集合在一起,放到一個init的初始化程序中進行。

(3)延時函數(shù)

通常使用的延時函數(shù)均采用自加的形式:

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

將其改為自減延時函數(shù):

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

兩個函數(shù)的延時效果相似,但幾乎所有的C編譯對后一種函數(shù)生成的代碼均比前一種代碼少1~3個字節(jié),因為幾乎所有的MCU均有為0轉(zhuǎn)移的指令,采用后一種方式能夠生成這類指令。在使用while循環(huán)時也一樣,使用自減指令控制循環(huán)會比使用自加指令控制循環(huán)生成的代碼更少1~3個字母。

但是,在循環(huán)中有通過循環(huán)變量“i”讀寫數(shù)組的指令時,使用預(yù)減循環(huán)有可能使數(shù)組超界,要引起注意。

(4)while循環(huán)和do…while循環(huán)

用while循環(huán)時有以下兩種循環(huán)形式:

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

或:

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

在這兩種循環(huán)中,使用do…while循環(huán)編譯后生成的代碼的長度短于while循環(huán)。

(5)循環(huán)展開

這是經(jīng)典的速度優(yōu)化,但許多編譯程序(如gcc -funroll-loops)能自動完成這個事,所以現(xiàn)在你自己來優(yōu)化這個顯得效果不明顯。

舊代碼:

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次,循環(huán)時間節(jié)約了90%。不過注意:對于中間變量或結(jié)果被更改的循環(huán),編譯程序往往拒絕展開(怕?lián)?zé)任唄),這時候就需要你自己來做展開工作了。

還有一點請注意,在有內(nèi)部指令cache的CPU上(如MMX芯片),因為循環(huán)展開的代碼很大,往往cache溢出,這時展開的代碼會頻繁地在CPU 的cache和內(nèi)存之間調(diào)來調(diào)去,又因為cache速度很高,所以此時循環(huán)展開反而會變慢。還有就是循環(huán)展開會影響矢量運算優(yōu)化。

(6)循環(huán)嵌套

把相關(guān)循環(huán)放到一個循環(huán)里,也會加快速度。

舊代碼:

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語句中根據(jù)發(fā)生頻率來進行case排序

Switch 可能轉(zhuǎn)化成多種不同算法的代碼。其中最常見的是跳轉(zhuǎn)表和比較鏈/樹。當(dāng)switch用比較鏈的方式轉(zhuǎn)化時,編譯器會產(chǎn)生if-else-if的嵌套代碼,并按照順序進行比較,匹配時就跳轉(zhuǎn)到滿足條件的語句執(zhí)行。所以,可以對case的值依照發(fā)生的可能性進行排序,把最有可能的放在第一位,這樣可以提高性能。

此外,在case中推薦使用小的連續(xù)的整數(shù),因為在這種情況下,所有的編譯器都可以把switch 轉(zhuǎn)化成跳轉(zhuǎn)表。

不好的代碼:

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語句轉(zhuǎn)為嵌套switch語句

當(dāng)switch語句中的case標號很多時,為了減少比較的次數(shù),明智的做法是把大switch語句轉(zhuǎn)為嵌套switch語句。把發(fā)生頻率高的case 標號放在一個switch語句中,并且是嵌套switch語句的最外層,發(fā)生相對頻率相對低的case標號放在另一個switch語句中。比如,下面的程序段把相對發(fā)生頻率低的情況放在缺省的case標號內(nèi)。

pMsg=ReceiveMessage();
switch (pMsg->type)
{
      case FREQUENT_MSG1:
        handleFrequentMsg();
        break;
      case FREQUENT_MSG2:
        handleFrequentMsg2();
        break;
        。。。。。。
      case FREQUENT_MSGn:
        handleFrequentMsgn();
        break;
      default:                     //嵌套部分用來處理不經(jīng)常發(fā)生的消息
        switch (pMsg->type)
        {
          case INFREQUENT_MSG1:
               handleInfrequentMsg1();
               break;
          case INFREQUENT_MSG2:
               handleInfrequentMsg2();
               break;
        。。。。。。
          case INFREQUENT_MSGm:
              handleInfrequentMsgm();
              break;
        }
}

如果switch中每一種情況下都有很多的工作要做,那么把整個switch語句用一個指向函數(shù)指針的表來替換會更加有效,比如下面的switch語句,有三種情況:

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

為了提高執(zhí)行速度,用下面這段代碼來替換這個上面的switch語句。

/*準備工作*/
int handleMsg1(void);
int handleMsg2(void);
int handleMsg3(void);
/*創(chuàng)建一個函數(shù)指針數(shù)組*/
int (*MsgFunction [])()={handleMsg1, handleMsg2, handleMsg3};
/*用下面這行更有效的代碼來替換switch語句*/
status=MsgFunction[ReceiveMessage()]();

(9)循環(huán)轉(zhuǎn)置

有些機器對JNZ(為0轉(zhuǎn)移)有特別的指令處理,速度非常快,如果你的循環(huán)對方向不敏感,可以由大向小循環(huán)。

舊代碼:

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

新代碼:

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

不過千萬注意,如果指針操作使用了i值,這種方法可能引起指針越界的嚴重錯誤(i = MAX+1;)。當(dāng)然,你可以通過對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)公用代碼塊

一些公用處理模塊,為了滿足各種不同的調(diào)用需要,往往在內(nèi)部采用了大量的if-then-else結(jié)構(gòu),這樣很不好,判斷語句如果太復(fù)雜,會消耗大量的時間的,應(yīng)該盡量減少公用代碼塊的使用(任何情況下,空間優(yōu)化和時間優(yōu)化都是對立的--東樓)。

當(dāng)然,如果僅僅是一個(3==x)之類的簡單判斷,適當(dāng)使用一下,也還是允許的。記住,優(yōu)化永遠是追求一種平衡,而不是走極端。

(11)提升循環(huán)的性能

要提升循環(huán)的性能,減少多余的常量計算非常有用(比如,不隨循環(huán)變化的計算)。

不好的代碼(在for()中包含不變的if()):

for( i 。。。)
{
if( CONSTANT0 )
{
  DoWork0( i );// 假設(shè)這里不改變CONSTANT0的值
}
else
{
DoWork1( i );// 假設(shè)這里不改變CONSTANT0的值
}
}

推薦的代碼:

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

如果已經(jīng)知道if()的值,這樣可以避免重復(fù)計算。雖然不好的代碼中的分支可以簡單地預(yù)測,但是由于推薦的代碼在進入循環(huán)前分支已經(jīng)確定,就可以減少對分支預(yù)測的依賴。

(12)選擇好的無限循環(huán)

編程中,我們常常需要用到無限循環(huán),常用的兩種方法是while (1)for (;;)。這兩種方法效果完全一樣,但那一種更好呢?然我們看看它們編譯后的代碼:

編譯前:

while (1);

編譯后:

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

編譯前:

for (;;);

編譯后:

jmp foo+23h

顯然,for (;;)指令少,不占用寄存器,而且沒有判斷、跳轉(zhuǎn),比while (1)好。

6、提高CPU的并行性

(1)使用并行代碼

盡可能把長的有依賴的代碼鏈分解成幾個可以在流水線執(zhí)行單元中并行執(zhí)行的沒有依賴的代碼鏈。很多高級語言,包括C++,并不對產(chǎn)生的浮點表達式重新排序,因為那是一個相當(dāng)復(fù)雜的過程。

需要注意的是,重排序的代碼和原來的代碼在代碼上一致并不等價于計算結(jié)果一致,因為浮點操作缺乏精確度。在一些情況下,這些優(yōu)化可能導(dǎo)致意料之外的結(jié)果。幸運的是,在大部分情況下,最后結(jié)果可能只有最不重要的位(即最低位)是錯誤的。

不好的代碼:

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)避免沒有必要的讀寫依賴

當(dāng)數(shù)據(jù)保存到內(nèi)存時存在讀寫依賴,即數(shù)據(jù)必須在正確寫入后才能再次讀取。雖然AMD Athlon等CPU有加速讀寫依賴延遲的硬件,允許在要保存的數(shù)據(jù)被寫入內(nèi)存前讀取出來,但是,如果避免了讀寫依賴并把數(shù)據(jù)保存在內(nèi)部寄存器中,速度會更快。在一段很長的又互相依賴的代碼鏈中,避免讀寫依賴顯得尤其重要。如果讀寫依賴發(fā)生在操作數(shù)組時,許多編譯器不能自動優(yōu)化代碼以避免讀寫依賴。

所以,推薦程序員手動去消除讀寫依賴,舉例來說,引進一個可以保存在寄存器中的臨時變量。這樣可以有很大的性能提升。下面一段代碼是一個例子:

不好的代碼:

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 

推薦的代碼:

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、循環(huán)不變計算

對于一些不需要循環(huán)變量參加運算的計算任務(wù)可以把它們放到循環(huán)外面,現(xiàn)在許多編譯器還是能自己干這件事,不過對于中間使用了變量的算式它們就不敢動了,所以很多情況下你還得自己干。對于那些在循環(huán)中調(diào)用的函數(shù),凡是沒必要執(zhí)行多次的操作通通提出來,放到一個init函數(shù)里,循環(huán)前調(diào)用。另外盡量減少喂食次數(shù),沒必要的話盡量不給它傳參,需要循環(huán)變量的話讓它自己建立一個靜態(tài)循環(huán)變量自己累加,速度會快一點。

還有就是結(jié)構(gòu)體訪問,東樓的經(jīng)驗,凡是在循環(huán)里對一個結(jié)構(gòu)體的兩個以上的元素執(zhí)行了訪問,就有必要建立中間變量了(結(jié)構(gòu)這樣,那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語言編譯器不做聚合優(yōu)化,而符合ANSI規(guī)范的新的編譯器可以自動完成這個優(yōu)化,看例子:

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

這種寫法當(dāng)然要得,但是沒有優(yōu)化

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

如果這么寫的話,一個符合ANSI規(guī)范的新的編譯器可以只計算b/c一次,然后將結(jié)果代入第二個式子,節(jié)約了一次除法運算。

8、函數(shù)優(yōu)化

(1)Inline函數(shù)

在C++中,關(guān)鍵字Inline可以被加入到任何函數(shù)的聲明中。這個關(guān)鍵字請求編譯器用函數(shù)內(nèi)部的代碼替換所有對于指出的函數(shù)的調(diào)用。

這樣做在兩個方面快于函數(shù)調(diào)用:第一,省去了調(diào)用指令需要的執(zhí)行時間;第二,省去了傳遞變元和傳遞過程需要的時間。但是使用這種方法在優(yōu)化程序速度的同時,程序長度變大了,因此需要更多的ROM。使用這種優(yōu)化在Inline函數(shù)頻繁調(diào)用并且只包含幾行代碼的時候是最有效的。

(2)不定義不使用的返回值

函數(shù)定義并不知道函數(shù)返回值是否被使用,假如返回值從來不會被用到,應(yīng)該使用void來明確聲明函數(shù)不返回任何值。

(3)減少函數(shù)調(diào)用參數(shù)

使用全局變量比函數(shù)傳遞參數(shù)更加有效率。這樣做去除了函數(shù)調(diào)用參數(shù)入棧和函數(shù)完成后參數(shù)出棧所需要的時間。然而決定使用全局變量會影響程序的模塊化和重入,故要慎重使用。

(4)所有函數(shù)都應(yīng)該有原型定義

一般來說,所有函數(shù)都應(yīng)該有原型定義。原型定義可以傳達給編譯器更多的可能用于優(yōu)化的信息。

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

盡可能使用常量(const)。C++ 標準規(guī)定,如果一個const聲明的對象的地址不被獲取,允許編譯器不對它分配儲存空間。這樣可以使代碼更有效率,而且可以生成更好的代碼。

(6)把本地函數(shù)聲明為靜態(tài)的(static)

如果一個函數(shù)只在實現(xiàn)它的文件中被使用,把它聲明為靜態(tài)的(static)以強制使用內(nèi)部連接。否則,默認的情況下會把函數(shù)定義為外部連接。這樣可能會影響某些編譯器的優(yōu)化——比如,自動內(nèi)聯(lián)。

9、采用遞歸

與LISP之類的語言不同,C語言一開始就病態(tài)地喜歡用重復(fù)代碼循環(huán),許多C程序員都是除非算法要求,堅決不用遞歸。

事實上,C編譯器們對優(yōu)化遞歸調(diào)用一點都不反感,相反,它們還很喜歡干這件事。只有在遞歸函數(shù)需要傳遞大量參數(shù),可能造成瓶頸的時候,才應(yīng)該使用循環(huán)代碼,其他時候,還是用遞歸好些。

10、變量

(1)register變量

在聲明局部變量的時候可以使用register關(guān)鍵字。這就使得編譯器把變量放入一個多用途的寄存器中,而不是在堆棧中,合理使用這種方法可以提高執(zhí)行速度。函數(shù)調(diào)用越是頻繁,越是可能提高代碼的速度。

在最內(nèi)層循環(huán)避免使用全局變量和靜態(tài)變量,除非你能確定它在循環(huán)周期中不會動態(tài)變化,大多數(shù)編譯器優(yōu)化變量都只有一個辦法,就是將他們置成寄存器變量,而對于動態(tài)變量,它們干脆放棄對整個表達式的優(yōu)化。盡量避免把一個變量地址傳遞給另一個函數(shù),雖然這個還很常用。C語言的編譯器們總是先假定每一個函數(shù)的變量都是內(nèi)部變量,這是由它的機制決定的,在這種情況下,它們的優(yōu)化完成得最好。但是,一旦一個變量有可能被別的函數(shù)改變,這幫兄弟就再也不敢把變量放到寄存器里了,嚴重影響速度。看例子:

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

因為d的地址被c函數(shù)使用,有可能被改變,編譯器不敢把它長時間的放在寄存器里,一旦運行到c(&d),編譯器就把它放回內(nèi)存,如果在循環(huán)里,會造成N次頻繁的在內(nèi)存和寄存器之間讀寫d的動作,眾所周知,CPU在系統(tǒng)總線上的讀寫速度慢得很。比如你的賽楊300,CPU主頻300,總線速度最多66M,為了一個總線讀,CPU可能要等4-5個周期,得。。得。。得。。想起來都打顫。

(2)同時聲明多個變量優(yōu)于單獨聲明變量

(3)短變量名優(yōu)于長變量名,應(yīng)盡量使變量名短一點

(4)在循環(huán)開始前聲明變量

11、使用嵌套的if結(jié)構(gòu)

在if結(jié)構(gòu)中如果要判斷的并列條件較多,最好將它們拆分成多個if結(jié)構(gòu),然后嵌套在一起,這樣可以避免無謂的判斷。

說明:

上面的優(yōu)化方案由王全明收集整理。很多資料來源網(wǎng)上,出處不祥,在此對所有作者一并致謝!

該方案主要是考慮到在嵌入式開發(fā)中對程序執(zhí)行速度的要求特別高,所以該方案主要是為了優(yōu)化程序的執(zhí)行速度。

注意:優(yōu)化是有側(cè)重點的,優(yōu)化是一門平衡的藝術(shù),它往往要以犧牲程序的可讀性或者增加代碼長度為代價。

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 算法
    +關(guān)注

    關(guān)注

    23

    文章

    4709

    瀏覽量

    95364
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7632

    瀏覽量

    141684
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4900

    瀏覽量

    70722

原文標題:C語言代碼優(yōu)化11種實用方法

文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    深入理解C語言C語言循環(huán)控制

    改變程序的執(zhí)行流程,使代碼更加靈活和可控。本文將詳細介紹這些語句的作用及其應(yīng)用場景,并通過示例代碼進行說明。Part.1break語句C語言中break語句有兩
    的頭像 發(fā)表于 04-29 18:49 ?1138次閱讀
    深入理解<b class='flag-5'>C</b><b class='flag-5'>語言</b>:<b class='flag-5'>C</b><b class='flag-5'>語言</b>循環(huán)控制

    語言模型的解碼策略與關(guān)鍵優(yōu)化總結(jié)

    本文系統(tǒng)性地闡述了大型語言模型(LargeLanguageModels,LLMs)中的解碼策略技術(shù)原理及其實踐應(yīng)用。通過深入分析各類解碼算法的工作機制、性能特征和優(yōu)化方法,為研究者和工程師提供了全面
    的頭像 發(fā)表于 02-18 12:00 ?582次閱讀
    大<b class='flag-5'>語言</b>模型的解碼策略與關(guān)鍵<b class='flag-5'>優(yōu)化</b>總結(jié)

    代碼加密、源代碼防泄漏c/c++與git服務(wù)器開發(fā)環(huán)境

    代碼加密對于很多研發(fā)性單位來說是至關(guān)重要的,當(dāng)然每家企業(yè)的業(yè)務(wù)需求不同所用的開發(fā)環(huán)境及開發(fā)語言也不盡相同,今天主要來講一下c++及git開發(fā)環(huán)境的源代碼防泄密保護方案。企業(yè)源
    的頭像 發(fā)表于 02-12 15:26 ?526次閱讀
    源<b class='flag-5'>代碼</b>加密、源<b class='flag-5'>代碼</b>防泄漏<b class='flag-5'>c</b>/<b class='flag-5'>c</b>++與git服務(wù)器開發(fā)環(huán)境

    分析C語言代碼結(jié)構(gòu)的設(shè)計問題

    來分析一個C語言代碼結(jié)構(gòu)的設(shè)計問題。 這段代碼,使用了兩次malloc,分別給 p1 和 p2 申請了內(nèi)存。用完后,內(nèi)存釋放,防止內(nèi)存泄漏。 大家覺得,這樣的
    的頭像 發(fā)表于 02-11 09:31 ?367次閱讀

    AKI跨語言調(diào)用庫神助攻C/C++代碼遷移至HarmonyOS NEXT

    )開發(fā)框架。它極大地簡化了JS與C/C++之間的跨語言訪問,為開發(fā)者提供了一邊界性編程體驗友好的解決方案。通過AKI,開發(fā)者可以使用讓代碼
    發(fā)表于 01-02 17:08

    Triton編譯器支持的編程語言

    編寫和優(yōu)化深度學(xué)習(xí)代碼。Python是一廣泛使用的高級編程語言,具有簡潔易讀、易于上手、庫豐富等特點,非常適合用于深度學(xué)習(xí)應(yīng)用的開發(fā)。 二、領(lǐng)域特定
    的頭像 發(fā)表于 12-24 17:33 ?963次閱讀

    深入理解C語言:循環(huán)語句的應(yīng)用與優(yōu)化技巧

    能讓你的代碼更加簡潔明了,還能顯著提升程序執(zhí)行效率。本文將詳細介紹C語言中的三常見循環(huán)結(jié)構(gòu)——while循環(huán)、for循環(huán)和do...while循環(huán),帶你深入理解它
    的頭像 發(fā)表于 12-07 01:11 ?633次閱讀
    深入理解<b class='flag-5'>C</b><b class='flag-5'>語言</b>:循環(huán)語句的應(yīng)用與<b class='flag-5'>優(yōu)化</b>技巧

    云端語言模型開發(fā)方法

    云端語言模型的開發(fā)是一個復(fù)雜而系統(tǒng)的過程,涉及數(shù)據(jù)準備、模型選擇、訓(xùn)練優(yōu)化、部署應(yīng)用等多個環(huán)節(jié)。下面,AI部落小編為您分享云端語言模型的開發(fā)方法
    的頭像 發(fā)表于 12-02 10:48 ?688次閱讀

    語言模型優(yōu)化生成管理方法

    語言模型的優(yōu)化生成管理是一個系統(tǒng)工程,涉及模型架構(gòu)、數(shù)據(jù)處理、內(nèi)容控制、實時響應(yīng)以及倫理監(jiān)管等多個層面。以下,是對大語言模型優(yōu)化生成管理方法
    的頭像 發(fā)表于 12-02 10:45 ?397次閱讀

    PLLATINUMSIM-SW是否有相關(guān)C語言代碼進行參考?

    PLLATINUMSIM-SW是否有相關(guān)C語言代碼進行參考,以達到對器件指標的準確評估直觀顯示。
    發(fā)表于 11-11 06:20

    TMS320LF240x DSP的C語言和匯編代碼快速入門

    電子發(fā)燒友網(wǎng)站提供《TMS320LF240x DSP的C語言和匯編代碼快速入門.pdf》資料免費下載
    發(fā)表于 10-18 10:14 ?1次下載
    TMS320LF240x DSP的<b class='flag-5'>C</b><b class='flag-5'>語言</b>和匯編<b class='flag-5'>代碼</b>快速入門

    C語言關(guān)鍵字--typedef

    C語言關(guān)鍵字使用方法學(xué)習(xí)指南!
    的頭像 發(fā)表于 10-07 12:44 ?614次閱讀

    hex文件怎么能轉(zhuǎn)回去c語言

    的差異。 然而,如果你想要從 .hex 文件中獲取一些有用的信息或者對程序進行分析,你可以采取以下步驟來盡可能接近原始的C語言邏輯: 反匯編 : 使用反匯編工具(如IDA Pro、Ghidra等)將 .hex 文件中的機器碼轉(zhuǎn)換為匯編
    的頭像 發(fā)表于 09-02 10:46 ?4409次閱讀

    hex文件如何查看原c語言代碼

    是處理器可以直接執(zhí)行的指令,而 C 語言代碼則是人類可讀的高級編程語言代碼。 然而,如果你想要從 .hex 文件中獲取一些有用的信息或者對程
    的頭像 發(fā)表于 09-02 10:37 ?4927次閱讀

    技術(shù)干貨驛站 ▏深入理解C語言:掌握常量,讓你的代碼更加穩(wěn)固高效!

    C語言的世界中,常量是一不可忽視的元素。無論你是在編寫簡單的代碼,還是構(gòu)建復(fù)雜的系統(tǒng),常量都能為你的程序帶來更高的穩(wěn)定性和可靠性。在這篇文章中,我們將深入探討
    的頭像 發(fā)表于 08-29 13:59 ?3534次閱讀
    技術(shù)干貨驛站 ▏深入理解<b class='flag-5'>C</b><b class='flag-5'>語言</b>:掌握常量,讓你的<b class='flag-5'>代碼</b>更加穩(wěn)固高效!