前沿拓展:
iccavr win10
就是有個(gè)帶符號字符的指針內(nèi)容是const,說明這來自個(gè)指針指向的內(nèi)容不能被修改,而程序需要一個(gè)可以被修改內(nèi)容的char *. 原因可能是你傳了字符串常量給函數(shù)
1、選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)
選擇一種合適的數(shù)據(jù)結(jié)構(gòu)很重要,如果在一堆隨機(jī)存放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。數(shù)組與指針語句具有十分密切的關(guān)系,一般來說,指針比較靈活簡潔,而數(shù)組則比較直觀,容易理解。對于大部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。
在許多種情況下,可以用指針運(yùn)算代替數(shù)組索引,這樣做常常能產(chǎn)生又快又短的代碼。與數(shù)組索引相比,指針一般能使代碼速度更快,占用空間更少。使用**數(shù)組時(shí)差異更明顯。下面的代碼作用是相同的,但是效率不一樣。
數(shù)組索引 指針運(yùn)算
For(;;){ p=array
A=array[t++]; for(;;){
a=*(p++);
。。。。。。。。。 。。。。。。
} }
指針方法的優(yōu)點(diǎn)是,array的地址每次裝入地址p后,在每次循環(huán)中只需對p增量**作。在數(shù)組索引方法中,每次循環(huán)中都必須根據(jù)t值求數(shù)組下標(biāo)的復(fù)雜運(yùn)算。
2、使用盡量小的數(shù)據(jù)類型
能夠使用字符型(char)定義的變量,就不要使用整型(int)變量來定義;能夠使用整型變量定義的變量就不要用長整型(long int),能不使用浮點(diǎn)型(float)變量就不要使用浮點(diǎn)型變量。當(dāng)然,在定義變量后不要超過變量的作用范圍,如果超過變量的范圍賦值,C編譯器并不報(bào)錯(cuò),但程序運(yùn)行結(jié)果卻錯(cuò)了,而且這樣的錯(cuò)誤很難發(fā)現(xiàn)。
在ICCAVR中,可以在Options中設(shè)定使用printf參數(shù),盡量使用基本型參數(shù)(%c、%d、%x、%X、%u和%s格式說明符),少用長整型參數(shù)(%ld、%lu、%lx和%lX格式說明符),至于浮點(diǎn)型的參數(shù)(%f)則盡量不要使用,其它C編譯器也一樣。在其它條件不變的情況下,使用%f參數(shù),會使生成的代碼的數(shù)量增加很多,執(zhí)行速度降低。
3、減少運(yùn)算的強(qiáng)度
(1)、查表(游戲程序員必修課)
一個(gè)聰明的游戲大蝦,基本上不會在自己的主循環(huán)里搞什么運(yùn)算工作,絕對是先計(jì)算好了,再到循環(huán)里查表。如果表很大,不好寫,就寫一個(gè)init函數(shù),在循環(huán)外臨時(shí)生成表格。
(2)、求余運(yùn)算
a=a%8;
可以改為:
a=a&7;
說明:位**作只需一個(gè)指令周期即可完成,而大部分的C編譯器的“%”運(yùn)算均是調(diào)用子程序來完 成,代碼長、執(zhí)行速度慢。通常,只要求是求2n方的余數(shù),均可使用位**作的方法來代替。
(3)、平方運(yùn)算
a=pow(a, 2.0);
可以改為:
a=a*a;
說明:在有內(nèi)置硬件乘法器的單片機(jī)中(如51系列),乘法運(yùn)算比求平方運(yùn)算快得多,因?yàn)楦↑c(diǎn)數(shù)的求平方是通過調(diào)用子程序來實(shí)現(xiàn)的,在自帶硬件乘法器的AVR單片機(jī)中,如ATMega163中,乘法運(yùn)算只需2個(gè)時(shí)鐘周期就可以完成。既使是在沒有內(nèi)置硬件乘法器的AVR單片機(jī)中,乘法運(yùn)算的子程序比平方運(yùn)算的子程序代碼短,執(zhí)行速度快。
(4)、用移位實(shí)現(xiàn)乘除法運(yùn)算
a=a*4;
b=b/4;
可以改為:
a=a<<2;
b=b>>2;
通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果乘以2n,都可以生成左移的代碼,而乘以其它的整數(shù)或除以任何數(shù),均調(diào)用乘除法子程序。用移位的方法得到代碼比調(diào)用乘除法子程序生成的代碼效率高。實(shí)際上,只要是乘以或除以一個(gè)整數(shù),均可以用移位的方法得到結(jié)果。
(5)、避免不必要的整數(shù)除法
整數(shù)除法是整數(shù)運(yùn)算中最慢的,所以應(yīng)該盡可能避免。一種可能減少整數(shù)除法的地方是連除,這里除法可以由乘法代替。這個(gè)替換的副作用是有可能在算乘積時(shí)會溢出,所以只能在一定范圍的除法中使用。
(6)、使用增量和減量**作符
在使用到加一和減一**作時(shí)盡量使用增量和減量**作符,因?yàn)樵隽糠Z句比賦值語句更快,原因在于對大多數(shù)CPU來說,對內(nèi)存字的增、減量**作不必明顯地使用取內(nèi)存和寫內(nèi)存的指令。顯然,不用取指令和存指令,增、減量**作執(zhí)行的速度加快,同時(shí)長度也縮短了。
(7)、使用復(fù)合賦值表達(dá)式
復(fù)合賦值表達(dá)式(如a-=1及a+=1等)都能夠生成高質(zhì)量的程序代碼。
(8)、提取公共的子表達(dá)式
在某些情況下,C++編譯器不能從浮點(diǎn)表達(dá)式中提出公共的子表達(dá)式,因?yàn)檫@意味著相當(dāng)于對表達(dá)式重新排序。需要特別指出的是,編譯器在提取公共子表達(dá)式前不能按照代數(shù)的等價(jià)關(guān)系重新安排表達(dá)式。這時(shí),程序員要手動地提出公共的子表達(dá)式。
4、結(jié)構(gòu)體成員的布局
很多編譯器有“使結(jié)構(gòu)體字,雙字或四字對齊”的選項(xiàng)。但是,還是需要改善結(jié)構(gòu)體成員的對齊,有些編譯器可能分配給結(jié)構(gòu)體成員空間的順序與他們聲明的不同。但是,有些編譯器并不提供這些功能,或者效果不好。所以,要在付出最少代價(jià)的情況下實(shí)現(xiàn)最好的結(jié)構(gòu)體和結(jié)構(gòu)體成員對齊,建議采取下列方法:
(1)按數(shù)據(jù)類型的長度排序
把結(jié)構(gòu)體的成員按照它們的類型長度排序,聲明成員時(shí)把長的類型放在短的前面。編譯器要求把長型數(shù)據(jù)類型存放在偶數(shù)地址邊界。在申明一個(gè)復(fù)雜的數(shù)據(jù)類型 (既有多字節(jié)數(shù)據(jù)又有單字節(jié)數(shù)據(jù)) 時(shí),應(yīng)該第一存放多字節(jié)數(shù)據(jù),第二再存放單字節(jié)數(shù)據(jù),這樣可以避免內(nèi)存的空洞。編譯器自動地把結(jié)構(gòu)的實(shí)例對齊在內(nèi)存的偶數(shù)邊界。
(2)把結(jié)構(gòu)體填充成最長類型長度的整倍數(shù)
把結(jié)構(gòu)體填充成最長類型長度的整倍數(shù)。照這樣,如果結(jié)構(gòu)體的第一個(gè)成員對齊了,所有整個(gè)結(jié)構(gòu)體自然也就對齊了。
(3)按數(shù)據(jù)類型的長度排序本地變量
當(dāng)編譯器分配給本地變量空間時(shí),它們的順序和它們在源代碼中聲明的順序一樣,和上一條規(guī)則一樣,應(yīng)該把長的變量放在短的變量前面。如果第一個(gè)變量對齊了,其它變量就會連續(xù)的存放,而且不用填充字節(jié)自然就會對齊。有些編譯器在分配變量時(shí)不會自動改變變量順序,有些編譯器不能產(chǎn)生4字節(jié)對齊的棧,所以4字節(jié)可能不對齊
(4)把頻繁使用的指針型參數(shù)拷貝到本地變量
避免在函數(shù)中頻繁使用指針型參數(shù)指向的值。因?yàn)榫幾g器不知道指針之間是否存在沖突,所以指針型參數(shù)往往不能被編譯器優(yōu)化。這樣數(shù)據(jù)不能被存放在寄存器中,而且明顯地占用了內(nèi)存帶寬。注意,很多編譯器有“假設(shè)不沖突”優(yōu)化開關(guān)(在VC里必須手動添加編譯器命令行/Oa或/Ow),這允許編譯器假設(shè)兩個(gè)不同的指針總是有不同的內(nèi)容,這樣就不用把指針型參數(shù)保存到本地變量。否則,請?jiān)诤瘮?shù)一開始把指針指向的數(shù)據(jù)保存到本地變量。如果需要的話,在函數(shù)結(jié)束前拷貝回去。
5、循環(huán)優(yōu)化
(1)、充分分解小的循環(huán)
要充分利用CPU的指令緩存,就要充分分解小的循環(huán)。特別是當(dāng)循環(huán)體本身很小的時(shí)候,分解循環(huán)可以提高性能。注意:很多編譯器并不能自動分解循環(huán)。
(2)、提取公共部分
對于一些不需要循環(huán)變量參加運(yùn)算的任務(wù)可以把它們放到循環(huán)外面,這里的任務(wù)包括表達(dá)式、函數(shù)的調(diào)用、指針運(yùn)算、數(shù)組訪問等,應(yīng)該將沒有必要執(zhí)行多次的**作全部**在一起,放到一個(gè)init的初始化程序中進(jìn)行。
(3)、延時(shí)函數(shù)
通常使用的延時(shí)函數(shù)均采用自加的形式:
void delay (void)
{
unsigned int i;
for (i=0;i<1000;i++) ;
}
將其改為自減延時(shí)函數(shù):
void delay (void)
{
unsigned int i;
for (i=1000;i>0;i–) ;
}
兩個(gè)函數(shù)的延時(shí)效果相似,但幾乎所有的C編譯對后一種函數(shù)生成的代碼均比前一種代碼少1~3個(gè)字節(jié),因?yàn)閹缀跛械腗CU均有為0轉(zhuǎn)移的指令,采用后一種方式能夠生成這類指令。在使用while循環(huán)時(shí)也一樣,使用自減指令控制循環(huán)會比使用自加指令控制循環(huán)生成的代碼更少1~3個(gè)字母。但是在循環(huán)中有通過循環(huán)變量“i”讀寫數(shù)組的指令時(shí),使用預(yù)減循環(huán)有可能使數(shù)組超界,要引起注意。
(4)、while循環(huán)和do…while循環(huán)
在這兩種循環(huán)中,使用do…while循環(huán)編譯后生成的代碼的長度短于while循環(huán)。
(5)、循環(huán)展開
這是經(jīng)典的速度優(yōu)化,但許多編譯程序(如gcc -funroll-loops)能自動完成這個(gè)事,所以現(xiàn)在你自己來優(yōu)化這個(gè)顯得效果不明顯。
(6)、循環(huán)嵌套
把相關(guān)循環(huán)放到一個(gè)循環(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ā)生頻率來進(jìn)行case排序
Switch 可能轉(zhuǎn)化成多種不同算法的代碼。其中最常見的是跳轉(zhuǎn)表和比較鏈/樹。當(dāng)switch用比較鏈的方式轉(zhuǎn)化時(shí),編譯器會產(chǎn)生if-else-if的嵌套代碼,并按照順序進(jìn)行比較,匹配時(shí)就跳轉(zhuǎn)到滿足條件的語句執(zhí)行。所以可以對case的值依照發(fā)生的可能性進(jìn)行排序,把最有可能的放在第一位,這樣可以提高性能。此外,在case中推薦使用小的連續(xù)的整數(shù),因?yàn)樵谶@種情況下,所有的編譯器都可以把switch 轉(zhuǎn)化成跳轉(zhuǎn)表。
(8)、將大的switch語句轉(zhuǎn)為嵌套switch語句
當(dāng)switch語句中的case標(biāo)號很多時(shí),為了減少比較的次數(shù),明智的做法是把大switch語句轉(zhuǎn)為嵌套switch語句。把發(fā)生頻率高的case 標(biāo)號放在一個(gè)switch語句中,并且是嵌套switch語句的最外層,發(fā)生相對頻率相對低的case標(biāo)號放在另一個(gè)switch語句中。比如,下面的程序段把相對發(fā)生頻率低的情況放在缺省的case標(biāo)號內(nèi)。
如果switch中每一種情況下都有很多的工作要做,那么把整個(gè)switch語句用一個(gè)指向函數(shù)指針的表來替換會更加有效。為了提高執(zhí)行速度,用下面這段代碼來替換這個(gè)上面的switch語句。
(9)、循環(huán)轉(zhuǎn)置
有些機(jī)器對JNZ(為0轉(zhuǎn)移)有特別的指令處理,速度非常快,如果你的循環(huán)對方向不敏感,可以由大向小循環(huán)。不過千萬注意,如果指針**作使用了i值,這種方法可能引起指針越界的嚴(yán)重錯(cuò)誤(i = MAX+1;)。當(dāng)然你可以通過對i做加減運(yùn)算來糾正,但是這樣就起不到加速的作用。
(10)、公用代碼塊
一些公用處理模塊,為了滿足各種不同的調(diào)用需要,往往在內(nèi)部采用了大量的if-then-else結(jié)構(gòu),這樣很不好,判斷語句如果太復(fù)雜,會消耗大量的時(shí)間的,應(yīng)該盡量減少公用代碼塊的使用。(任何情況下,空間優(yōu)化和時(shí)間優(yōu)化都是對立的–東樓)。當(dāng)然,如果僅僅是一個(gè)(3==x)之類的簡單判斷,適當(dāng)使用一下,也還是允許的。記住,優(yōu)化永遠(yuǎn)是追求一種平衡,而不是走極端。
(11)提升循環(huán)的性能
要提升循環(huán)的性能,減少多余的常量計(jì)算非常有用(比如,不隨循環(huán)變化的計(jì)算)。
如果已經(jīng)知道if()的值,這樣可以避免重復(fù)計(jì)算。雖然不好的代碼中的分支可以簡單地預(yù)測,但是由于推薦的代碼在進(jìn)入循環(huán)前分支已經(jīng)確定,就可以減少對分支預(yù)測的依賴。
(12)、選擇好的無限循環(huán)
在編程中,我們常常需要用到無限循環(huán),常用的兩種方法是while (1) 和 for (;;)。這兩種方法效果完全一樣,但那一種更好呢?編譯后,for (;;)指令少,不占用寄存器,而且沒有判斷、跳轉(zhuǎn),比while (1)好。
6、提高CPU的并行性
(1)使用并行代碼
盡可能把長的有依賴的代碼鏈分解成幾個(gè)可以在流水線執(zhí)行單元中并行執(zhí)行的沒有依賴的代碼鏈。很多高級語言,包括C++,并不對產(chǎn)生的浮點(diǎn)表達(dá)式重新排序,因?yàn)槟鞘且粋€(gè)相當(dāng)復(fù)雜的過程。需要注意的是,重排序的代碼和原來的代碼在代碼上一致并不等價(jià)于計(jì)算結(jié)果一致,因?yàn)楦↑c(diǎn)**作缺乏精確度。在一些情況下,這些優(yōu)化可能導(dǎo)致意料之外的結(jié)果。幸運(yùn)的是,在大部分情況下,最后結(jié)果可能只有最不重要的位(即最低位)是錯(cuò)誤的。
(2)避免沒有必要的讀寫依賴
當(dāng)數(shù)據(jù)保存到內(nèi)存時(shí)存在讀寫依賴,即數(shù)據(jù)必須在正確寫入后才能再次讀取。雖然AMD Athlon等CPU有加速讀寫依賴延遲的硬件,允許在要保存的數(shù)據(jù)被寫入內(nèi)存前讀取出來,但是,如果避免了讀寫依賴并把數(shù)據(jù)保存在內(nèi)部寄存器中,速度會更快。在一段很長的又互相依賴的代碼鏈中,避免讀寫依賴顯得尤其重要。如果讀寫依賴發(fā)生在**作數(shù)組時(shí),許多編譯器不能自動優(yōu)化代碼以避免讀寫依賴。所以推薦程序員手動去消除讀寫依賴,舉例來說,引進(jìn)一個(gè)可以保存在寄存器中的臨時(shí)變量。這樣可以有很大的性能提升。
7、循環(huán)不變計(jì)算
對于一些不需要循環(huán)變量參加運(yùn)算的計(jì)算任務(wù)可以把它們放到循環(huán)外面,現(xiàn)在許多編譯器還是能自己干這件事,不過對于中間使用了變量的算式它們就不敢動了,所以很多情況下你還得自己干。對于那些在循環(huán)中調(diào)用的函數(shù),凡是沒必要執(zhí)行多次的**作通通提出來,放到一個(gè)init函數(shù)里,循環(huán)前調(diào)用。另外盡量減少喂食次數(shù),沒必要的話盡量不給它傳參,需要循環(huán)變量的話讓它自己建立一個(gè)靜態(tài)循環(huán)變量自己累加,速度會快一點(diǎn)。
還有就是結(jié)構(gòu)體訪問,東樓的經(jīng)驗(yàn),凡是在循環(huán)里對一個(gè)結(jié)構(gòu)體的兩個(gè)以上的元素執(zhí)行了訪問,就有必要建立中間變量了(結(jié)構(gòu)這樣,那C++的對象呢?想想看)。
8、函數(shù)優(yōu)化
(1)Inline函數(shù)
在C++中,關(guān)鍵字Inline可以被加入到任何函數(shù)的聲明中。這個(gè)關(guān)鍵字請求編譯器用函數(shù)內(nèi)部的代碼替換所有對于指出的函數(shù)的調(diào)用。這樣做在兩個(gè)方面快于函數(shù)調(diào)用:第一,省去了調(diào)用指令需要的執(zhí)行時(shí)間;第二,省去了傳遞變元和傳遞過程需要的時(shí)間。但是使用這種方法在優(yōu)化程序速度的同時(shí),程序長度變大了,因此需要更多的ROM。使用這種優(yōu)化在Inline函數(shù)頻繁調(diào)用并且只包含幾行代碼的時(shí)候是最有效的。
(2)不定義不使用的返回值
函數(shù)定義并不知道函數(shù)返回值是否被使用,假如返回值從來不會被用到,應(yīng)該使用void來明確聲明函數(shù)不返回任何值。
(3)減少函數(shù)調(diào)用參數(shù)
使用全局變量比函數(shù)傳遞參數(shù)更加**。這樣做去除了函數(shù)調(diào)用參數(shù)入棧和函數(shù)完成后參數(shù)出棧所需要的時(shí)間。然而決定使用全局變量會影響程序的模塊化和重入,故要慎重使用。
(4)所有函數(shù)都應(yīng)該有原型定義
一般來說,所有函數(shù)都應(yīng)該有原型定義。原型定義可以傳達(dá)給編譯器更多的可能用于優(yōu)化的信息。
(5)盡可能使用常量(const)
盡可能使用常量(const)。C++ 標(biāo)準(zhǔn)規(guī)定,如果一個(gè)const聲明的對象的地址不被獲取,允許編譯器不對它分配儲存空間。這樣可以使代碼更**,而且可以生成更好的代碼。
(6)把本地函數(shù)聲明為靜態(tài)的(static)
如果一個(gè)函數(shù)只在實(shí)現(xiàn)它的文件中被使用,把它聲明為靜態(tài)的(static)以強(qiáng)制使用內(nèi)部連接。否則,默認(rèn)的情況下會把函數(shù)定義為外部連接。這樣可能會影響某些編譯器的優(yōu)化——比如,自動內(nèi)聯(lián)。
9、采用遞歸
與LISP 之類的語言不同,C語言一開始就病態(tài)地喜歡用重復(fù)代碼循環(huán),許多C程序員都是除非算法要求,堅(jiān)決不用遞歸。事實(shí)上,C編譯器們對優(yōu)化遞歸調(diào)用一點(diǎn)都不反感,相反,它們還很喜歡干這件事。只有在遞歸函數(shù)需要傳遞大量參數(shù),可能造成瓶頸的時(shí)候,才應(yīng)該使用循環(huán)代碼,其他時(shí)候,還是用遞歸好些。
10、變量
(1)register變量
在聲明局部變量的時(shí)候可以使用register關(guān)鍵字。這就使得編譯器把變量放入一個(gè)多用途的寄存器中,而不是在堆棧中,合理使用這種方法可以提高執(zhí)行速度。函數(shù)調(diào)用越是頻繁,越是可能提高代碼的速度。
在最內(nèi)層循環(huán)避免使用全局變量和靜態(tài)變量,除非你能確定它在循環(huán)周期中不會動態(tài)變化,大多數(shù)編譯器優(yōu)化變量都只有一個(gè)辦法,就是將他們置成寄存器變量,而對于動態(tài)變量,它們干脆放棄對整個(gè)表達(dá)式的優(yōu)化。盡量避免把一個(gè)變量地址傳遞給另一個(gè)函數(shù),雖然這個(gè)還很常用。C語言的編譯器們總是先假定每一個(gè)函數(shù)的變量都是內(nèi)部變量,這是由它的機(jī)制決定的,在這種情況下,它們的優(yōu)化完成得最好。但是,一旦一個(gè)變量有可能被別的函數(shù)改變,這幫兄弟就再也不敢把變量放到寄存器里了,嚴(yán)重影響速度。看例子:
a = b();
c(&d);
因?yàn)閐 的地址被c函數(shù)使用,有可能被改變,編譯器不敢把它長時(shí)間的放在寄存器里,一旦運(yùn)行到c(&d),編譯器就把它放回內(nèi)存,如果在循環(huán)里,會造成N 次頻繁的在內(nèi)存和寄存器之間讀寫d的動作,眾所周知,CPU在系統(tǒng)總線上的讀寫速度慢得很。比如你的賽楊300,CPU主頻300,總線速度最多66M,為了一個(gè)總線讀,CPU可能要等4-5個(gè)周期,得。。得。。得。。想起來都打顫。
(2)、同時(shí)聲明多個(gè)變量優(yōu)于單獨(dú)聲明變量
(3)、短變量名優(yōu)于長變量名,應(yīng)盡量使變量名短一點(diǎn)
(4)、在循環(huán)開始前聲明變量
11、使用嵌套的if結(jié)構(gòu)
在if結(jié)構(gòu)中如果要判斷的并列條件較多,最好將它們拆分成多個(gè)if結(jié)構(gòu),第二嵌套在一起,這樣可以避免無謂的判斷。
轉(zhuǎn)載自曉的blog
大家如果對編程感興趣,想了解更多的編程知識,解決編程問題,我們這里有java高手,C++/C高手,windows/Linux高手,android/ios高手,請大家關(guān)注我的微信**:程序員互動聯(lián)盟or coder_online
拓展知識:
原創(chuàng)文章,作者:九賢生活小編,如若轉(zhuǎn)載,請注明出處:http://m.xiesong.cn/128297.html