代码 优化 指南 实践
C代碼優(yōu)化方案
?
?
?
?
?
?
?
?
?
?
華中科技大學(xué)計(jì)算機(jī)學(xué)院
?
姓名:??? 王全明
QQ:?????? 375288012
Email:????quanming1119@163.com
目錄
目錄
C代碼優(yōu)化方案
1、選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)
2、使用盡量小的數(shù)據(jù)類型
3、減少運(yùn)算的強(qiáng)度
(1)、查表(游戲程序員必修課)
(2)、求余運(yùn)算
(3)、平方運(yùn)算
(4)、用移位實(shí)現(xiàn)乘除法運(yùn)算
(5)、避免不必要的整數(shù)除法
(6)、使用增量和減量操作符
(7)、使用復(fù)合賦值表達(dá)式
(8)、提取公共的子表達(dá)式
4、結(jié)構(gòu)體成員的布局
(1)按數(shù)據(jù)類型的長(zhǎng)度排序
(2)把結(jié)構(gòu)體填充成最長(zhǎng)類型長(zhǎng)度的整倍數(shù)
(3)按數(shù)據(jù)類型的長(zhǎng)度排序本地變量
(4)把頻繁使用的指針型參數(shù)拷貝到本地變量
5、循環(huán)優(yōu)化
(1)、充分分解小的循環(huán)
(2)、提取公共部分
(3)、延時(shí)函數(shù)
(4)、while循環(huán)和do…while循環(huán)
(6)、循環(huán)展開
(6)、循環(huán)嵌套
(7)、Switch語(yǔ)句中根據(jù)發(fā)生頻率來(lái)進(jìn)行case排序
(8)、將大的switch語(yǔ)句轉(zhuǎn)為嵌套switch語(yǔ)句
(9)、循環(huán)轉(zhuǎn)置
(10)、公用代碼塊
(11)提升循環(huán)的性能
(12)、選擇好的無(wú)限循環(huán)
6、提高CPU的并行性
(1)使用并行代碼
(2)避免沒(méi)有必要的讀寫依賴
7、循環(huán)不變計(jì)算
8、函數(shù)
(1)Inline函數(shù)
(2)不定義不使用的返回值
(3)減少函數(shù)調(diào)用參數(shù)
(4)所有函數(shù)都應(yīng)該有原型定義
(5)盡可能使用常量(const)
(6)把本地函數(shù)聲明為靜態(tài)的(static)
9、采用遞歸
10、變量
(1)register變量
(2)、同時(shí)聲明多個(gè)變量?jī)?yōu)于單獨(dú)聲明變量
(3)、短變量名優(yōu)于長(zhǎng)變量名,應(yīng)盡量使變量名短一點(diǎn)
(4)、在循環(huán)開始前聲明變量
11、使用嵌套的if結(jié)構(gòu)
C代碼優(yōu)化方案
1、選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)
選擇一種合適的數(shù)據(jù)結(jié)構(gòu)很重要,如果在一堆隨機(jī)存放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。數(shù)組與指針語(yǔ)句具有十分密切的關(guān)系,一般來(lái)說(shuō),指針比較靈活簡(jiǎn)潔,而數(shù)組則比較直觀,容易理解。對(duì)于大部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。
在許多種情況下,可以用指針運(yùn)算代替數(shù)組索引,這樣做常常能產(chǎn)生又快又短的代碼。與數(shù)組索引相比,指針一般能使代碼速度更快,占用空間更少。使用多維數(shù)組時(shí)差異更明顯。下面的代碼作用是相同的,但是效率不一樣。
??? 數(shù)組索引???????????????指針運(yùn)算
??? For(;;){??????????????? p=array
??? A=array[t++];????????? for(;;){
??????????????????????????????? a=*(p++);
?? ?。。。。。。。。。?????????????????。。。。。。
??? }????????????????????? }
指針?lè)椒ǖ膬?yōu)點(diǎn)是,array的地址每次裝入地址p后,在每次循環(huán)中只需對(duì)p增量操作。在數(shù)組索引方法中,每次循環(huán)中都必須根據(jù)t值求數(shù)組下標(biāo)的復(fù)雜運(yùn)算。
2、使用盡量小的數(shù)據(jù)類型
能夠使用字符型(char)定義的變量,就不要使用整型(int)變量來(lái)定義;能夠使用整型變量定義的變量就不要用長(zhǎng)整型(long int),能不使用浮點(diǎn)型(float)變量就不要使用浮點(diǎn)型變量。當(dāng)然,在定義變量后不要超過(guò)變量的作用范圍,如果超過(guò)變量的范圍賦值,C編譯器并不報(bào)錯(cuò),但程序運(yùn)行結(jié)果卻錯(cuò)了,而且這樣的錯(cuò)誤很難發(fā)現(xiàn)。
在ICCAVR中,可以在Options中設(shè)定使用printf參數(shù),盡量使用基本型參數(shù)(%c、%d、%x、%X、%u和%s格式說(shuō)明符),少用長(zhǎng)整型參數(shù)(%ld、%lu、%lx和%lX格式說(shuō)明符),至于浮點(diǎn)型的參數(shù)(%f)則盡量不要使用,其它C編譯器也一樣。在其它條件不變的情況下,使用%f參數(shù),會(huì)使生成的代碼的數(shù)量增加很多,執(zhí)行速度降低。
3、減少運(yùn)算的強(qiáng)度
(1)、查表(游戲程序員必修課)
一個(gè)聰明的游戲大蝦,基本上不會(huì)在自己的主循環(huán)里搞什么運(yùn)算工作,絕對(duì)是先計(jì)算好了,再到循環(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];
??? }
如果表很大,不好寫,就寫一個(gè)init函數(shù),在循環(huán)外臨時(shí)生成表格。
(2)、求余運(yùn)算
??? a=a%8;
可以改為:
??? a=a&7;
說(shuō)明:位操作只需一個(gè)指令周期即可完成,而大部分的C編譯器的“%”運(yùn)算均是調(diào)用子程序來(lái)完成,代碼長(zhǎng)、執(zhí)行速度慢。通常,只要求是求2n方的余數(shù),均可使用位操作的方法來(lái)代替。
(3)、平方運(yùn)算
??? a=pow(a, 2.0);
可以改為:
??? a=a*a;
說(shuō)明:在有內(nèi)置硬件乘法器的單片機(jī)中(如51系列),乘法運(yùn)算比求平方運(yùn)算快得多,因?yàn)楦↑c(diǎn)數(shù)的求平方是通過(guò)調(diào)用子程序來(lái)實(shí)現(xiàn)的,在自帶硬件乘法器的AVR單片機(jī)中,如ATMega163中,乘法運(yùn)算只需2個(gè)時(shí)鐘周期就可以完成。既使是在沒(méi)有內(nèi)置硬件乘法器的AVR單片機(jī)中,乘法運(yùn)算的子程序比平方運(yùn)算的子程序代碼短,執(zhí)行速度快。
如果是求3次方,如:
??? a=pow(a,3。0);
更改為:
??? a=a*a*a;
則效率的改善更明顯。
(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é)果,如:
??? a=a*9
可以改為:
a=(a<<3)+a
采用運(yùn)算量更小的表達(dá)式替換原來(lái)的表達(dá)式,下面是一個(gè)經(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ùn)算快 */
??? y = x * x; ????????????? /* 乘法比平方運(yùn)算快 */
??? z = (y << 5) + y; ???????? /* 位移乘法比乘法快 */
??? for (i = h = 0; i < MAX; i++)
??? {
??????? h += 14;??????????????? /* 加法比乘法快 */
??????? printf("%d", h);
}
(5)、避免不必要的整數(shù)除法
整數(shù)除法是整數(shù)運(yùn)算中最慢的,所以應(yīng)該盡可能避免。一種可能減少整數(shù)除法的地方是連除,這里除法可以由乘法代替。這個(gè)替換的副作用是有可能在算乘積時(shí)會(huì)溢出,所以只能在一定范圍的除法中使用。
???????? 不好的代碼:
int i, j, k, m;
m = i / j / k;
???????????? 推薦的代碼:
int i, j, k, m;
m = i / (j * k);
(6)、使用增量和減量操作符
??? 在使用到加一和減一操作時(shí)盡量使用增量和減量操作符,因?yàn)樵隽糠Z(yǔ)句比賦值語(yǔ)句更快,原因在于對(duì)大多數(shù)CPU來(lái)說(shuō),對(duì)內(nèi)存字的增、減量操作不必明顯地使用取內(nèi)存和寫內(nèi)存的指令,比如下面這條語(yǔ)句:
x=x+1;
模仿大多數(shù)微機(jī)匯編語(yǔ)言為例,產(chǎn)生的代碼類似于:
??? move A,x????? ;把x從內(nèi)存取出存入累加器A
??? add A,1??????? ;累加器A加1
store x????????? ;把新值存回x
如果使用增量操作符,生成的代碼如下:
??? incr x?????????? ;x加1
顯然,不用取指令和存指令,增、減量操作執(zhí)行的速度加快,同時(shí)長(zhǎng)度也縮短了。
(7)、使用復(fù)合賦值表達(dá)式
復(fù)合賦值表達(dá)式(如a-=1及a+=1等)都能夠生成高質(zhì)量的程序代碼。
(8)、提取公共的子表達(dá)式
在某些情況下,C++編譯器不能從浮點(diǎn)表達(dá)式中提出公共的子表達(dá)式,因?yàn)檫@意味著相當(dāng)于對(duì)表達(dá)式重新排序。需要特別指出的是,編譯器在提取公共子表達(dá)式前不能按照代數(shù)的等價(jià)關(guān)系重新安排表達(dá)式。這時(shí),程序員要手動(dòng)地提出公共的子表達(dá)式(在VC.NET里有一項(xiàng)“全局優(yōu)化”選項(xiàng)可以完成此工作,但效果就不得而知了)。
不好的代碼:
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)體字,雙字或四字對(duì)齊”的選項(xiàng)。但是,還是需要改善結(jié)構(gòu)體成員的對(duì)齊,有些編譯器可能分配給結(jié)構(gòu)體成員空間的順序與他們聲明的不同。但是,有些編譯器并不提供這些功能,或者效果不好。所以,要在付出最少代價(jià)的情況下實(shí)現(xiàn)最好的結(jié)構(gòu)體和結(jié)構(gòu)體成員對(duì)齊,建議采取下列方法:
(1)按數(shù)據(jù)類型的長(zhǎng)度排序
把結(jié)構(gòu)體的成員按照它們的類型長(zhǎng)度排序,聲明成員時(shí)把長(zhǎng)的類型放在短的前面。編譯器要求把長(zhǎng)型數(shù)據(jù)類型存放在偶數(shù)地址邊界。在申明一個(gè)復(fù)雜的數(shù)據(jù)類型 (既有多字節(jié)數(shù)據(jù)又有單字節(jié)數(shù)據(jù))時(shí),應(yīng)該首先存放多字節(jié)數(shù)據(jù),然后再存放單字節(jié)數(shù)據(jù),這樣可以避免內(nèi)存的空洞。編譯器自動(dòng)地把結(jié)構(gòu)的實(shí)例對(duì)齊在內(nèi)存的偶數(shù)邊界。
(2)把結(jié)構(gòu)體填充成最長(zhǎng)類型長(zhǎng)度的整倍數(shù)
把結(jié)構(gòu)體填充成最長(zhǎng)類型長(zhǎng)度的整倍數(shù)。照這樣,如果結(jié)構(gòu)體的第一個(gè)成員對(duì)齊了,所有整個(gè)結(jié)構(gòu)體自然也就對(duì)齊了。下面的例子演示了如何對(duì)結(jié)構(gòu)體成員進(jìn)行重新排序:
不好的代碼,普通順序:
struct
{
???????????? ????????????? char a[5];
?????? long k;
double x;
} baz;
?
推薦的代碼,新的順序并手動(dòng)填充了幾個(gè)字節(jié):
struct
{
???????????? ?????? ?????? double x;
???????????? ????????????? long k;
???????????? ????????????? char a[5];
char pad[7];
} baz;
這個(gè)規(guī)則同樣適用于類的成員的布局。
(3)按數(shù)據(jù)類型的長(zhǎng)度排序本地變量
當(dāng)編譯器分配給本地變量空間時(shí),它們的順序和它們?cè)谠创a中聲明的順序一樣,和上一條規(guī)則一樣,應(yīng)該把長(zhǎng)的變量放在短的變量前面。如果第一個(gè)變量對(duì)齊了,其它變量就會(huì)連續(xù)的存放,而且不用填充字節(jié)自然就會(huì)對(duì)齊。有些編譯器在分配變量時(shí)不會(huì)自動(dòng)改變變量順序,有些編譯器不能產(chǎn)生4字節(jié)對(duì)齊的棧,所以4字節(jié)可能不對(duì)齊。下面這個(gè)例子演示了本地變量聲明的重新排序:
????? ?????? 不好的代碼,普通順序
short ga, gu, gi;
long foo, bar;
double x, y, z[3];
char a, b;
float baz;
推薦的代碼,改進(jìn)的順序
double z[3];
double x, y;
long foo, bar;
float baz;
short ga, gu, gi;?
(4)把頻繁使用的指針型參數(shù)拷貝到本地變量
避免在函數(shù)中頻繁使用指針型參數(shù)指向的值。因?yàn)榫幾g器不知道指針之間是否存在沖突,所以指針型參數(shù)往往不能被編譯器優(yōu)化。這樣數(shù)據(jù)不能被存放在寄存器中,而且明顯地占用了內(nèi)存帶寬。注意,很多編譯器有“假設(shè)不沖突”優(yōu)化開關(guān)(在VC里必須手動(dòng)添加編譯器命令行/Oa或/Ow),這允許編譯器假設(shè)兩個(gè)不同的指針總是有不同的內(nèi)容,這樣就不用把指針型參數(shù)保存到本地變量。否則,請(qǐng)?jiān)诤瘮?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)體本身很小的時(shí)候,分解循環(huán)可以提高性能。注意:很多編譯器并不能自動(dòng)分解循環(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)、提取公共部分
對(duì)于一些不需要循環(huán)變量參加運(yùn)算的任務(wù)可以把它們放到循環(huán)外面,這里的任務(wù)包括表達(dá)式、函數(shù)的調(diào)用、指針運(yùn)算、數(shù)組訪問(wèn)等,應(yīng)該將沒(méi)有必要執(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編譯對(duì)后一種函數(shù)生成的代碼均比前一種代碼少1~3個(gè)字節(jié),因?yàn)?strong>幾乎所有的MCU均有為0轉(zhuǎn)移的指令,采用后一種方式能夠生成這類指令。在使用while循環(huán)時(shí)也一樣,使用自減指令控制循環(huán)會(huì)比使用自加指令控制循環(huán)生成的代碼更少1~3個(gè)字母。但是在循環(huán)中有通過(guò)循環(huán)變量“i”讀寫數(shù)組的指令時(shí),使用預(yù)減循環(huán)有可能使數(shù)組超界,要引起注意。
(4)、while循環(huán)和do…while循環(huán)
用while循環(huán)時(shí)有以下兩種循環(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)編譯后生成的代碼的長(zhǎng)度短于while循環(huán)。
(6)、循環(huán)展開
這是經(jīng)典的速度優(yōu)化,但許多編譯程序(如gcc -funroll-loops)能自動(dòng)完成這個(gè)事,所以現(xiàn)在你自己來(lái)優(yōu)化這個(gè)顯得效果不明顯。
舊代碼:
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)時(shí)間節(jié)約了90%。不過(guò)注意:對(duì)于中間變量或結(jié)果被更改的循環(huán),編譯程序往往拒絕展開,(怕?lián)?zé)任唄),這時(shí)候就需要你自己來(lái)做展開工作了。
還有一點(diǎn)請(qǐng)注意,在有內(nèi)部指令cache的CPU上(如MMX芯片),因?yàn)檠h(huán)展開的代碼很大,往往cache溢出,這時(shí)展開的代碼會(huì)頻繁地在CPU的cache和內(nèi)存之間調(diào)來(lái)調(diào)去,又因?yàn)?span style="font-family:Times New Roman">cache速度很高,所以此時(shí)循環(huán)展開反而會(huì)變慢。還有就是循環(huán)展開會(huì)影響矢量運(yùn)算優(yōu)化。
(6)、循環(huán)嵌套
把相關(guān)循環(huán)放到一個(gè)循環(huán)里,也會(huì)加快速度。
舊代碼:
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語(yǔ)句中根據(jù)發(fā)生頻率來(lái)進(jìn)行case排序
Switch 可能轉(zhuǎn)化成多種不同算法的代碼。其中最常見(jiàn)的是跳轉(zhuǎn)表和比較鏈/樹。當(dāng)switch用比較鏈的方式轉(zhuǎn)化時(shí),編譯器會(huì)產(chǎn)生if-else-if的嵌套代碼,并按照順序進(jìn)行比較,匹配時(shí)就跳轉(zhuǎn)到滿足條件的語(yǔ)句執(zhí)行。所以可以對(duì)case的值依照發(fā)生的可能性進(jìn)行排序,把最有可能的放在第一位,這樣可以提高性能。此外,在case中推薦使用小的連續(xù)的整數(shù),因?yàn)樵谶@種情況下,所有的編譯器都可以把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語(yǔ)句轉(zhuǎn)為嵌套switch語(yǔ)句
當(dāng)switch語(yǔ)句中的case標(biāo)號(hào)很多時(shí),為了減少比較的次數(shù),明智的做法是把大switch語(yǔ)句轉(zhuǎn)為嵌套switch語(yǔ)句。把發(fā)生頻率高的case標(biāo)號(hào)放在一個(gè)switch語(yǔ)句中,并且是嵌套switch語(yǔ)句的最外層,發(fā)生相對(duì)頻率相對(duì)低的case標(biāo)號(hào)放在另一個(gè)switch語(yǔ)句中。比如,下面的程序段把相對(duì)發(fā)生頻率低的情況放在缺省的case標(biāo)號(hào)內(nèi)。
pMsg=ReceiveMessage();
??????? switch (pMsg->type)
??????? {
??????? case FREQUENT_MSG1:
??????? handleFrequentMsg();
??????? break;
??????? case FREQUENT_MSG2:
??????? handleFrequentMsg2();
??????? break;
??????? 。。。。。。
??????? case FREQUENT_MSGn:
??????? handleFrequentMsgn();
??????? break;
??????? default:????????????????????//嵌套部分用來(lái)處理不經(jīng)常發(fā)生的消息
??????? switch (pMsg->type)
??????? {
??????? case INFREQUENT_MSG1:
??????? handleInfrequentMsg1();
??????? break;
??????? case INFREQUENT_MSG2:
??????? handleInfrequentMsg2();
??????? break;
??????? 。。。。。。
??????? case INFREQUENT_MSGm:
??????? handleInfrequentMsgm();
??????? break;
??????? }
??????? }
如果switch中每一種情況下都有很多的工作要做,那么把整個(gè)switch語(yǔ)句用一個(gè)指向函數(shù)指針的表來(lái)替換會(huì)更加有效,比如下面的switch語(yǔ)句,有三種情況:
??? enum MsgType{Msg1, Msg2, Msg3}
??????? switch (ReceiveMessage()
??????? {
??????? case Msg1;
??????? 。。。。。。
???? ???case Msg2;
??????? 。。。。。
??????? case Msg3;
??????? 。。。。。
??????? }
為了提高執(zhí)行速度,用下面這段代碼來(lái)替換這個(gè)上面的switch語(yǔ)句。
??????? /*準(zhǔn)備工作*/
??????? int handleMsg1(void);
??????? int handleMsg2(void);
??????? int handleMsg3(void);
??????? /*創(chuàng)建一個(gè)函數(shù)指針數(shù)組*/
??????? int (*MsgFunction [])()={handleMsg1, handleMsg2, handleMsg3};
??????? /*用下面這行更有效的代碼來(lái)替換switch語(yǔ)句*/
??????? status=MsgFunction[ReceiveMessage()]();
(9)、循環(huán)轉(zhuǎn)置
有些機(jī)器對(duì)JNZ(為0轉(zhuǎn)移)有特別的指令處理,速度非常快,如果你的循環(huán)對(duì)方向不敏感,可以由大向小循環(huán)。
舊代碼:
? ?? ?????? ??for (i = 1; i <= MAX; i++)
????? ?????? ? {
???? ???????? ????????????? ?。。。
??? ???????????? ?????? ??}
新代碼:
? ?? ?????? ??i = MAX+1;
?? ?????? ?while (--i)
??? ???????????? ?????? ?{
??????? ???? ????????????? 。。。
??? ???????????? ?????? ?}
不過(guò)千萬(wàn)注意,如果指針操作使用了i值,這種方法可能引起指針越界的嚴(yán)重錯(cuò)誤(i = MAX+1;)。當(dāng)然你可以通過(guò)對(duì)i做加減運(yùn)算來(lá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),這樣很不好,判斷語(yǔ)句如果太復(fù)雜,會(huì)消耗大量的時(shí)間的,應(yīng)該盡量減少公用代碼塊的使用。(任何情況下,空間優(yōu)化和時(shí)間優(yōu)化都是對(duì)立的--東樓)。當(dāng)然,如果僅僅是一個(gè)(3==x)之類的簡(jiǎn)單判斷,適當(dāng)使用一下,也還是允許的。記住,優(yōu)化永遠(yuǎn)是追求一種平衡,而不是走極端。
(11)提升循環(huán)的性能
要提升循環(huán)的性能,減少多余的常量計(jì)算非常有用(比如,不隨循環(huán)變化的計(jì)算)。
不好的代碼(在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ù)計(jì)算。雖然不好的代碼中的分支可以簡(jiǎn)單地預(yù)測(cè),但是由于推薦的代碼在進(jìn)入循環(huán)前分支已經(jīng)確定,就可以減少對(duì)分支預(yù)測(cè)的依賴。
(12)、選擇好的無(wú)限循環(huán)
在編程中,我們常常需要用到無(wú)限循環(huán),常用的兩種方法是while (1) 和 for (;;)。這兩種方法效果完全一樣,但那一種更好呢?然我們看看它們編譯后的代碼:
編譯前:
while (1);
編譯后:
mov eax,1
test eax,eax
je foo+23h
jmp foo+18h?
編譯前:
for (;;);
編譯后:
jmp foo+23h
顯然,for (;;)指令少,不占用寄存器,而且沒(méi)有判斷、跳轉(zhuǎn),比while (1)好。
6、提高CPU的并行性
(1)使用并行代碼
盡可能把長(zhǎng)的有依賴的代碼鏈分解成幾個(gè)可以在流水線執(zhí)行單元中并行執(zhí)行的沒(méi)有依賴的代碼鏈。很多高級(jí)語(yǔ)言,包括C++,并不對(duì)產(chǎn)生的浮點(diǎn)表達(dá)式重新排序,因?yàn)槟鞘且粋€(gè)相當(dāng)復(fù)雜的過(guò)程。需要注意的是,重排序的代碼和原來(lái)的代碼在代碼上一致并不等價(jià)于計(jì)算結(jié)果一致,因?yàn)楦↑c(diǎn)操作缺乏精確度。在一些情況下,這些優(yōu)化可能導(dǎo)致意料之外的結(jié)果。幸運(yùn)的是,在大部分情況下,最后結(jié)果可能只有最不重要的位(即最低位)是錯(cuò)誤的。
不好的代碼:
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 路分解是因?yàn)檫@樣使用了4段流水線浮點(diǎn)加法,浮點(diǎn)加法的每一個(gè)段占用一個(gè)時(shí)鐘周期,保證了最大的資源利用率。
(2)避免沒(méi)有必要的讀寫依賴
當(dāng)數(shù)據(jù)保存到內(nèi)存時(shí)存在讀寫依賴,即數(shù)據(jù)必須在正確寫入后才能再次讀取。雖然AMD Athlon等CPU有加速讀寫依賴延遲的硬件,允許在要保存的數(shù)據(jù)被寫入內(nèi)存前讀取出來(lái),但是,如果避免了讀寫依賴并把數(shù)據(jù)保存在內(nèi)部寄存器中,速度會(huì)更快。在一段很長(zhǎng)的又互相依賴的代碼鏈中,避免讀寫依賴顯得尤其重要。如果讀寫依賴發(fā)生在操作數(shù)組時(shí),許多編譯器不能自動(dòng)優(yōu)化代碼以避免讀寫依賴。所以推薦程序員手動(dòng)去消除讀寫依賴,舉例來(lái)說(shuō),引進(jìn)一個(gè)可以保存在寄存器中的臨時(shí)變量。這樣可以有很大的性能提升。下面一段代碼是一個(gè)例子:
不好的代碼:
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、循環(huán)不變計(jì)算
對(duì)于一些不需要循環(huán)變量參加運(yùn)算的計(jì)算任務(wù)可以把它們放到循環(huán)外面,現(xiàn)在許多編譯器還是能自己干這件事,不過(guò)對(duì)于中間使用了變量的算式它們就不敢動(dòng)了,所以很多情況下你還得自己干。對(duì)于那些在循環(huán)中調(diào)用的函數(shù),凡是沒(méi)必要執(zhí)行多次的操作通通提出來(lái),放到一個(gè)init函數(shù)里,循環(huán)前調(diào)用。另外盡量減少喂食次數(shù),沒(méi)必要的話盡量不給它傳參,需要循環(huán)變量的話讓它自己建立一個(gè)靜態(tài)循環(huán)變量自己累加,速度會(huì)快一點(diǎn)。
還有就是結(jié)構(gòu)體訪問(wèn),東樓的經(jīng)驗(yàn),凡是在循環(huán)里對(duì)一個(gè)結(jié)構(gòu)體的兩個(gè)以上的元素執(zhí)行了訪問(wèn),就有必要建立中間變量了(結(jié)構(gòu)這樣,那C++的對(duì)象呢?想想看),看下面的例子:
舊代碼:
??? 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ǔ)言編譯器不做聚合優(yōu)化,而符合ANSI規(guī)范的新的編譯器可以自動(dòng)完成這個(gè)優(yōu)化,看例子:
??? float a, b, c, d, f, g;
??? 。。。
??? a = b / c * d;
??? f = b * g / c;
這種寫法當(dāng)然要得,但是沒(méi)有優(yōu)化
??? float a, b, c, d, f, g;
??? 。。。
??? a = b / c * d;
??? f = b / c * g;
如果這么寫的話,一個(gè)符合ANSI規(guī)范的新的編譯器可以只計(jì)算b/c一次,然后將結(jié)果代入第二個(gè)式子,節(jié)約了一次除法運(yùn)算。
8、函數(shù)優(yōu)化
?(1)Inline函數(shù)
在C++中,關(guān)鍵字Inline可以被加入到任何函數(shù)的聲明中。這個(gè)關(guān)鍵字請(qǐng)求編譯器用函數(shù)內(nèi)部的代碼替換所有對(duì)于指出的函數(shù)的調(diào)用。這樣做在兩個(gè)方面快于函數(shù)調(diào)用:第一,省去了調(diào)用指令需要的執(zhí)行時(shí)間;第二,省去了傳遞變?cè)蛡鬟f過(guò)程需要的時(shí)間。但是使用這種方法在優(yōu)化程序速度的同時(shí),程序長(zhǎng)度變大了,因此需要更多的ROM。使用這種優(yōu)化在Inline函數(shù)頻繁調(diào)用并且只包含幾行代碼的時(shí)候是最有效的。
(2)不定義不使用的返回值
函數(shù)定義并不知道函數(shù)返回值是否被使用,假如返回值從來(lái)不會(huì)被用到,應(yīng)該使用void來(lái)明確聲明函數(shù)不返回任何值。
(3)減少函數(shù)調(diào)用參數(shù)
??? 使用全局變量比函數(shù)傳遞參數(shù)更加有效率。這樣做去除了函數(shù)調(diào)用參數(shù)入棧和函數(shù)完成后參數(shù)出棧所需要的時(shí)間。然而決定使用全局變量會(huì)影響程序的模塊化和重入,故要慎重使用。
(4)所有函數(shù)都應(yīng)該有原型定義
一般來(lái)說(shuō),所有函數(shù)都應(yīng)該有原型定義。原型定義可以傳達(dá)給編譯器更多的可能用于優(yōu)化的信息。
(5)盡可能使用常量(const)
盡可能使用常量(const)。C++標(biāo)準(zhǔn)規(guī)定,如果一個(gè)const聲明的對(duì)象的地址不被獲取,允許編譯器不對(duì)它分配儲(chǔ)存空間。這樣可以使代碼更有效率,而且可以生成更好的代碼。
(6)把本地函數(shù)聲明為靜態(tài)的(static)
如果一個(gè)函數(shù)只在實(shí)現(xiàn)它的文件中被使用,把它聲明為靜態(tài)的(static)以強(qiáng)制使用內(nèi)部連接。否則,默認(rèn)的情況下會(huì)把函數(shù)定義為外部連接。這樣可能會(huì)影響某些編譯器的優(yōu)化——比如,自動(dòng)內(nèi)聯(lián)。
9、采用遞歸
與LISP之類的語(yǔ)言不同,C語(yǔ)言一開始就病態(tài)地喜歡用重復(fù)代碼循環(huán),許多C程序員都是除非算法要求,堅(jiān)決不用遞歸。事實(shí)上,C編譯器們對(duì)優(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)周期中不會(huì)動(dòng)態(tài)變化,大多數(shù)編譯器優(yōu)化變量都只有一個(gè)辦法,就是將他們置成寄存器變量,而對(duì)于動(dòng)態(tài)變量,它們干脆放棄對(duì)整個(gè)表達(dá)式的優(yōu)化。盡量避免把一個(gè)變量地址傳遞給另一個(gè)函數(shù),雖然這個(gè)還很常用。C語(yǔ)言的編譯器們總是先假定每一個(gè)函數(shù)的變量都是內(nèi)部變量,這是由它的機(jī)制決定的,在這種情況下,它們的優(yōu)化完成得最好。但是,一旦一個(gè)變量有可能被別的函數(shù)改變,這幫兄弟就再也不敢把變量放到寄存器里了,嚴(yán)重影響速度。看例子:
a = b();
c(&d);
因?yàn)閐的地址被c函數(shù)使用,有可能被改變,編譯器不敢把它長(zhǎng)時(shí)間的放在寄存器里,一旦運(yùn)行到c(&d),編譯器就把它放回內(nèi)存,如果在循環(huán)里,會(huì)造成N次頻繁的在內(nèi)存和寄存器之間讀寫d的動(dòng)作,眾所周知,CPU在系統(tǒng)總線上的讀寫速度慢得很。比如你的賽楊300,CPU主頻300,總線速度最多66M,為了一個(gè)總線讀,CPU可能要等4-5個(gè)周期,得。。得。。得。。想起來(lái)都打顫。
(2)、同時(shí)聲明多個(gè)變量?jī)?yōu)于單獨(dú)聲明變量
(3)、短變量名優(yōu)于長(zhǎng)變量名,應(yīng)盡量使變量名短一點(diǎn)
(4)、在循環(huán)開始前聲明變量
11、使用嵌套的if結(jié)構(gòu)
在if結(jié)構(gòu)中如果要判斷的并列條件較多,最好將它們拆分成多個(gè)if結(jié)構(gòu),然后嵌套在一起,這樣可以避免無(wú)謂的判斷。
說(shuō)明:
上面的優(yōu)化方案由王全明收集整理。很多資料來(lái)源與網(wǎng)上,出處不祥,在此對(duì)所有作者一并致謝!
該方案主要是考慮到在嵌入式開發(fā)中對(duì)程序執(zhí)行速度的要求特別高,所以該方案主要是為了優(yōu)化程序的執(zhí)行速度。
注意:優(yōu)化是有側(cè)重點(diǎn)的,優(yōu)化是一門平衡的藝術(shù),它往往要以犧牲程序的可讀性或者增加代碼長(zhǎng)度為代價(jià)。
(任何情況下,空間優(yōu)化和時(shí)間優(yōu)化都是對(duì)立的--東樓)。
2.?
代碼優(yōu)化概要
我編寫程序至今有35年了,我做了很多關(guān)于程序執(zhí)行速度方面優(yōu)化的工( 一個(gè)示例),我也看過(guò)其它人做的優(yōu)化。我發(fā)現(xiàn)有兩個(gè)最基本的優(yōu)化技術(shù)總是被人所忽略。 注意,這兩個(gè)技術(shù)并不是避免時(shí)機(jī)不成熟的優(yōu)化。并不是把冒泡排序變成快速排序(算法優(yōu)化)。也不是語(yǔ)言或是編譯器的優(yōu)化。也不是把 i*4寫成i<<2 的優(yōu)化。 這兩個(gè)技術(shù)是:使用這兩個(gè)技術(shù)的人將會(huì)成功地寫出運(yùn)行快的代碼,不會(huì)使用這兩個(gè)技術(shù)的人則不行。下面讓我為你細(xì)細(xì)道來(lái)。
使用一個(gè) Profiler
我們知道,程序運(yùn)行時(shí)的90%的時(shí)間是用在了10%的代碼上。我發(fā)現(xiàn)這并不準(zhǔn)確。一次又一次地,我發(fā)現(xiàn),幾乎所有的程序會(huì)在1%的代碼上花了99%的運(yùn)行時(shí)間。但是,是哪個(gè)1%?一個(gè)好的Profiler可以告訴你這個(gè)答案。就算我們需要使用100個(gè)小時(shí)在這1%的代碼上進(jìn)行優(yōu)化,也比使用100個(gè)小時(shí)在其它99%的代碼上優(yōu)化產(chǎn)生的效益要高得多得多。 問(wèn)題是什么?人們不用profiler?不是。我工作過(guò)的一個(gè)地方使用了一個(gè)華麗而奢侈的Profiler,但是自從購(gòu)買這個(gè)Profiler后,它的包裝3年來(lái)還是那么的暫新。為什么人們不用?我真的不知道。有一次,我和我的同事去了一個(gè)負(fù)載過(guò)大的交易所,我同事堅(jiān)持說(shuō)他知道哪里是瓶頸,畢竟,他是一個(gè)很有經(jīng)驗(yàn)的專家。最終,我把我的Profiler在他的項(xiàng)目上運(yùn)行了一下,我們發(fā)現(xiàn)那個(gè)瓶頸完全在一個(gè)意想不到的地方。 就像是賽車一樣。團(tuán)隊(duì)是贏在傳感器和日志上,這些東西提供了所有的一切。你可以調(diào)整一下賽車手的褲子以讓其在比賽過(guò)程中更舒服,但是這不會(huì)讓你贏得比賽,也不會(huì)讓你更有競(jìng)爭(zhēng)力。如果你不知道你的速度上不去是因?yàn)橐妗⑴艢庋b置、空體動(dòng)力學(xué)、輪胎氣壓,或是賽車手,那么你將無(wú)法獲勝。編程為什么會(huì)不同呢?只要沒(méi)有測(cè)量,你就永遠(yuǎn)無(wú)法進(jìn)步。 這個(gè)世界上有太多可以使用的Profiler了。隨便找一個(gè)你就可以看到你的函數(shù)的調(diào)用層次,調(diào)用的次數(shù),以前每條代碼的時(shí)間分解表(甚至可以到匯編級(jí))。我看過(guò)太多的程序員回避使用Profiler,而是把時(shí)間花在那些無(wú)用的,錯(cuò)誤的方向上的“優(yōu)化”,而被其競(jìng)爭(zhēng)對(duì)手所羞辱。(譯者陳皓注:使用Profiler時(shí),重點(diǎn)需要關(guān)注:1)花時(shí)間多的函數(shù)以優(yōu)化其算法,2)調(diào)用次數(shù)巨多的函數(shù)——如果一個(gè)函數(shù)每秒被調(diào)用300K次,你只需要優(yōu)化出0.001毫秒,那也是相當(dāng)大的優(yōu)化。這就是作者所謂的1%的代碼占用了99%的CPU時(shí)間)
查看匯編代碼
幾年前,我有一個(gè)同事,Mary Bailey,她在華盛頓大學(xué)教矯正代數(shù)(remedial algebra),有一次,她在黑板上寫下: x + 3 = 5 然后問(wèn)他的學(xué)生“求解x”,然后學(xué)生們不知道答案。于是她寫下:__ + 3 = 5 然后,再問(wèn)學(xué)生“填空”,所有的學(xué)生都可以回答了。未知數(shù)x就像是一個(gè)有魔法的字母讓大家都在想“x意味著代數(shù),而我沒(méi)有學(xué)過(guò)代數(shù),所以我就不知道這個(gè)怎么做”。 匯編程序就是編程世界的代數(shù)。如果某人問(wèn)我“inline函數(shù)是否被編譯器展開了?”或是問(wèn)我“如果我寫下i*4,編譯器會(huì)把其優(yōu)化為左移位操作嗎?”。這個(gè)時(shí)候,我都會(huì)建議他們看看編譯器的匯編碼。這樣的回答是不是很粗暴和無(wú)用?通常,在我這樣回答了提問(wèn)者后,提問(wèn)都通常都會(huì)說(shuō),對(duì)不起,我不知道什么是匯編!甚至C++的專家都會(huì)這么回答。 匯編語(yǔ)言是最簡(jiǎn)單的編程語(yǔ)言了(就算是和C++相比也是這樣的),如:
ADD ESI,x
就是(C風(fēng)格的代碼)
ESI += x;
而:
CALL foo
則是:
foo();
細(xì)節(jié)因?yàn)镃PU的種類而不同,但這就是其如何工作的。有時(shí)候,我們甚至都不需要細(xì)節(jié),只需要看看匯編碼的長(zhǎng)啥樣,然后和源代碼比一比,你就可以知道匯編代碼很多很多了。 那么,這又如何幫助代碼優(yōu)化?舉個(gè)例子,我?guī)啄昵罢J(rèn)識(shí)一個(gè)程序員認(rèn)為他應(yīng)該去發(fā)現(xiàn)一個(gè)新的更快的算法。他有一個(gè)benchmark來(lái)證明這個(gè)算法,并且其寫了一篇非常漂亮的文章關(guān)于他的這個(gè)算法。但是,有人看了一下其原來(lái)算法以及新算法的匯編,發(fā)現(xiàn)了他的改進(jìn)版本的算法允許其編譯器把兩個(gè)除法操作變成了一個(gè)。這和算法真的沒(méi)有什么關(guān)系。我們知道除法操作是一個(gè)很昂貴的操作,并且在其算法中,這倆個(gè)除法操作還在一個(gè)內(nèi)嵌循環(huán)中,所以,他的改進(jìn)版的算法當(dāng)然要快一些。但,只需要在原來(lái)的算法上做一點(diǎn)點(diǎn)小的改動(dòng)——使用一個(gè)除法操作,那么其原來(lái)的算法將會(huì)和新的一樣快。而他的新發(fā)現(xiàn)什么也不是。 下一個(gè)例子,一個(gè)D用戶張貼了一個(gè) benchmark 來(lái)顯示 dmd (Digital Mars D 編譯器)在整型算法上的很糟糕,而ldc (LLVM D 編譯器) 就好很多了。對(duì)于這樣的結(jié)果,其相當(dāng)?shù)挠幸庖?jiàn)。我迅速地看了一下匯編,發(fā)現(xiàn)兩個(gè)編譯器編譯出來(lái)相當(dāng)?shù)囊恢?#xff0c;并沒(méi)有什么明顯的東西要對(duì)2:1這么大的不同而負(fù)責(zé)。但是我們看到有一個(gè)對(duì)long型整數(shù)的除法,這個(gè)除法調(diào)用了運(yùn)行庫(kù)。而這個(gè)庫(kù)成為消耗時(shí)間的殺手,其它所有的加減法都沒(méi)有速度上的影響。出乎意料地,benchmark 和算法代碼生成一點(diǎn)關(guān)系也沒(méi)有,完全就是long型整數(shù)的除法的問(wèn)題。這暴露了在dmd的運(yùn)行庫(kù)中的long型除法的實(shí)現(xiàn)很差。修正后就可以提高速度。所以,這和編譯器沒(méi)有什么關(guān)系,但是如果不看匯編,你將無(wú)法發(fā)現(xiàn)這一切。 查看匯編代碼經(jīng)常會(huì)給你一些意想不到的東西讓你知道為什么程序的性能是那樣。一些意想不到的函數(shù)調(diào)用,預(yù)料不到的自傲,以及不應(yīng)該存在的東西,等等其實(shí)所有的一切。但也不需要成為一個(gè)匯編代碼的黑客才能干的事。
結(jié)論
如果你覺(jué)得需要程序有更好的執(zhí)行速度,那么,最基本的方法就是使用一個(gè)profiler和愿意去查看一下其匯編代碼以找到程序的瓶頸。只有找到了程序的瓶頸,此時(shí)才是真正在思考如何去改進(jìn)的時(shí)候,比如思考一個(gè)更好的算法,使用更快的語(yǔ)言優(yōu)化,等等。 常規(guī)的做法是制勝法寶是挑選一個(gè)最佳的算法而不是進(jìn)行微優(yōu)化。雖然這種做法是無(wú)可異議的,但是有兩件事情是學(xué)校沒(méi)有教給你而需要你重點(diǎn)注意的。第一個(gè)也是最重要的,如果你優(yōu)化的算法沒(méi)沒(méi)有參與到你程序性能中的算法,那么你優(yōu)化他只是在浪費(fèi)時(shí)間和精力,并且還轉(zhuǎn)移了你的注意力讓你錯(cuò)過(guò)了應(yīng)該要去優(yōu)化的部分。第二點(diǎn),算法的性能總和處理的數(shù)據(jù)密切相關(guān)的,就算是冒泡排序有那么多的笑柄,但是如果其處理的數(shù)據(jù)基本是排好序的,只有其中幾個(gè)數(shù)據(jù)是未排序的,那么冒泡排序也是所有排序算法里性能最好的。所以,擔(dān)心沒(méi)有使用好的算法而不去測(cè)量,只會(huì)浪費(fèi)時(shí)間,無(wú)論是你的還是計(jì)算機(jī)的。 就好像賽車零件的訂購(gòu)速底是不會(huì)讓你更靠進(jìn)冠軍(就算是你正確安裝零件也不會(huì)),沒(méi)有Profiler,你不會(huì)知道問(wèn)題在哪里,不去看匯編,你可能知道問(wèn)題所在,但你往往不知道為什么。 (全文完)
3.?
優(yōu)化代碼
通過(guò)優(yōu)化可執(zhí)行文件,可在較快執(zhí)行速度和較小代碼大小之間實(shí)現(xiàn)平衡。 本主題討論了 Visual C++ 提供的可幫助您優(yōu)化代碼的一些機(jī)制。
語(yǔ)言功能下面的主題介紹了 C/C++ 語(yǔ)言中的一些優(yōu)化功能。
優(yōu)化雜注和關(guān)鍵字可在代碼中使用以提高性能的關(guān)鍵字和雜注的列表。
專門影響執(zhí)行速度或代碼大小的 /O 編譯器選項(xiàng)的列表。
Rvalue 引用支持移動(dòng)語(yǔ)義的實(shí)現(xiàn)。 如果移動(dòng)語(yǔ)義用于實(shí)現(xiàn)模板庫(kù),則使用這些模板的應(yīng)用程序的性能可顯著提高。
優(yōu)化雜注
如果經(jīng)過(guò)優(yōu)化的某個(gè)代碼節(jié)導(dǎo)致錯(cuò)誤或速度減慢,則可以使用 optimize 雜注對(duì)該代碼節(jié)關(guān)閉優(yōu)化。
用兩個(gè)雜注將代碼括起來(lái),如下所示:
#pragma optimize("", off) // some code here #pragma optimize("", on) 編程慣例在用優(yōu)化的方式編譯代碼時(shí),您可能會(huì)注意到一些附加的警告消息。 此行為是預(yù)期行為,因?yàn)橐恍┚鎯H與優(yōu)化的代碼有關(guān)。 如果您注意到這些警告,則可以避免許多優(yōu)化問(wèn)題。
矛盾的是,為了速度而對(duì)程序進(jìn)行優(yōu)化可能會(huì)導(dǎo)致代碼運(yùn)行速度減慢。 這是因?yàn)橐恍榱怂俣榷M(jìn)行的優(yōu)化會(huì)增加代碼大小。 例如,內(nèi)聯(lián)函數(shù)可消除函數(shù)調(diào)用的開銷。但是內(nèi)聯(lián)太多代碼可能會(huì)使程序很大,致使虛擬內(nèi)存頁(yè)的錯(cuò)誤數(shù)增加。 因此,通過(guò)消除函數(shù)調(diào)用獲得的速度可能會(huì)丟失在內(nèi)存交調(diào)中。
下面的主題討論了良好的編程做法。
提高時(shí)間關(guān)鍵代碼的技巧更好的編碼技術(shù)可產(chǎn)生更好的性能。 本主題建議了一些可幫助您確保時(shí)間關(guān)鍵代碼部分的執(zhí)行令人滿意的編碼技術(shù)。
提供了有關(guān)如何以最佳方式優(yōu)化應(yīng)用程序的一般準(zhǔn)則。
由于優(yōu)化可能會(huì)更改編譯器創(chuàng)建的代碼,因此建議您調(diào)試應(yīng)用程序并測(cè)量其性能,隨后優(yōu)化代碼。
下面的主題提供有關(guān)如何進(jìn)行調(diào)試的基本信息。
-
使用 Visual Studio 進(jìn)行調(diào)試
-
創(chuàng)建發(fā)行版本時(shí)遇到的常見(jiàn)問(wèn)題
下面的主題提供有關(guān)如何進(jìn)行調(diào)試的更高級(jí)信息。
-
如何:調(diào)試優(yōu)化的代碼
-
為何浮點(diǎn)數(shù)可能丟失精度
以下各個(gè)主題提供有關(guān)如何優(yōu)化生成、加載和執(zhí)行代碼的信息。
-
提高編譯器吞吐量
-
使用沒(méi)有 () 的函數(shù)名不產(chǎn)生代碼
-
Optimizing Inline Assembly
-
為 ATL 項(xiàng)目指定編譯器優(yōu)化
-
加載時(shí)應(yīng)使用哪些優(yōu)化技術(shù)來(lái)提高客戶端應(yīng)用程序的性能?
-
有關(guān)以下內(nèi)容的更多信息如何縮短 DLL 方法加載時(shí)間的更多信息,請(qǐng)參見(jiàn) MSDN 庫(kù)網(wǎng)站上的“MSDN 雜志”中“Under the Hood”(深入實(shí)質(zhì))專欄下的“Optimizing DLL Load Time Performance”(優(yōu)化 DLL 加載時(shí)間性能)。
-
有關(guān)以下內(nèi)容的更多信息如何在應(yīng)用程序中最大程度減少分頁(yè)的更多信息,請(qǐng)參見(jiàn) MSDN 庫(kù) 網(wǎng)站上的“MSDN 雜志”中“Bugslayer”專欄下的“Improving Runtime Performance with the Smooth Working Set Tool”(使用 Smooth 工作集工具提高運(yùn)行時(shí)性能)和“Improving Runtime Performance with the Smooth Working Set Tool—Part 2”(使用 Smooth 工作集工具提高運(yùn)行時(shí)性能(第 2 部分))。
4.?
C++代碼優(yōu)化方法總結(jié)
優(yōu)化是一個(gè)非常大的主題,本文并不是去深入探討性能分析理論,算法的效率,況且我也沒(méi)有這個(gè)能力。我只是想把一些可以簡(jiǎn)單的應(yīng)用到你的C++代碼中的優(yōu)化技術(shù)總結(jié)在這里,這樣,當(dāng)你遇到幾種不同的編程策略的時(shí)候,就可以對(duì)每種策略的性能進(jìn)行一個(gè)大概的估計(jì)。這也是本文的目的之所在。
一. 優(yōu)化之前
在進(jìn)行優(yōu)化之前,我們首先應(yīng)該做的是發(fā)現(xiàn)我們代碼的瓶頸(bottleneck)在哪里。然而當(dāng)你做這件事情的時(shí)候切忌從一個(gè)debug-version進(jìn)行推斷,因?yàn)閐ebug-version中包含了許多額外的代碼。一個(gè)debug-version可執(zhí)行體要比release-version大出40%。那些額外的代碼都是用來(lái)支持調(diào)試的,比如說(shuō)符號(hào)的查找。大多數(shù)實(shí)現(xiàn)都為debug-version和release-version提供了不同的operator new以及庫(kù)函數(shù)。而且,一個(gè)release-version的執(zhí)行體可能已經(jīng)通過(guò)多種途徑進(jìn)行了優(yōu)化,包括不必要的臨時(shí)對(duì)象的消除,循環(huán)展開,把對(duì)象移入寄存器,內(nèi)聯(lián)等等。
另外,我們要把調(diào)試和優(yōu)化區(qū)分開來(lái),它們是在完成不同的任務(wù)。 debug-version 是用來(lái)追捕bugs以及檢查程序是否有邏輯上的問(wèn)題。release-version則是用來(lái)做一些性能上的調(diào)整以及進(jìn)行優(yōu)化。
下面就讓我們來(lái)看看有哪些代碼優(yōu)化技術(shù)吧:
二. 聲明的放置
程序中變量和對(duì)象的聲明放在什么位置將會(huì)對(duì)性能產(chǎn)生顯著影響。同樣,對(duì)postfix和prefix運(yùn)算符的選擇也會(huì)影響性能。這一部分我們集中討論四個(gè)問(wèn)題:初始化v.s 賦值,在程序確實(shí)要使用的地方放置聲明,構(gòu)造函數(shù)的初始化列表,prefix v.s postfix運(yùn)算符。
(1) 請(qǐng)使用初始化而不是賦值
在C語(yǔ)言中只允許在一個(gè)函數(shù)體的開頭進(jìn)行變量的聲明,然而在C++中聲明可以出現(xiàn)在程序的任何位置。這樣做的目的是希望把對(duì)象的聲明拖延到確實(shí)要使用它的時(shí)候再進(jìn)行。這樣做可以有兩個(gè)好處:1. 確保了對(duì)象在它被使用前不會(huì)被程序的其他部分惡意修改。如果對(duì)象在開頭就被聲明然而卻在20行以后才被使用的話,就不能做這樣的保證。2. 使我們有機(jī)會(huì)通過(guò)用初始化取代賦值來(lái)達(dá)到性能的提升,從前聲明只能放在開頭,然而往往開始的時(shí)候我們還沒(méi)有獲得我們想要的值,因此初始化所帶來(lái)的好處就無(wú)法被應(yīng)用。但是現(xiàn)在我們可以在我們獲得了想要的值的時(shí)候直接進(jìn)行初始化,從而省去了一步。注意,或許對(duì)于基本類型來(lái)說(shuō),初始化和賦值之間可能不會(huì)有什么差異,但是對(duì)于用戶定義的類型來(lái)說(shuō),二者就會(huì)帶來(lái)顯著的不同,因?yàn)橘x值會(huì)多進(jìn)行一次函數(shù)調(diào)用----operator =。因此當(dāng)我們?cè)谫x值和初始化之間進(jìn)行選擇的話,初始化應(yīng)該是我們的首選。
(2) 把聲明放在合適的位置上
在一些場(chǎng)合,通過(guò)移動(dòng)聲明到合適的位置所帶來(lái)的性能提升應(yīng)該引起我們足夠的重視。例如:
bool is_C_Needed();
void use()
{
C c1;
if (is_C_Needed() == false)
{
return; //c1 was not needed
}
//use c1 here
return;
}
上面這段代碼中對(duì)象c1即使在有可能不使用它的情況下也會(huì)被創(chuàng)建,這樣我們就會(huì)為它付出不必要的花費(fèi),有可能你會(huì)說(shuō)一個(gè)對(duì)象c1能浪費(fèi)多少時(shí)間,但是如果是這種情況呢:C c1[1000];我想就不是說(shuō)浪費(fèi)就浪費(fèi)了。但是我們可以通過(guò)移動(dòng)聲明c1的位置來(lái)改變這種情況:
void use()
{
if (is_C_Needed() == false)
{
return; //c1 was not needed
}
C c1; //moved from the block"s beginning
//use c1 here
return;
}
怎么樣,程序的性能是不是已經(jīng)得到很大的改善了呢?因此請(qǐng)仔細(xì)分析你的代碼,把聲明放在合適的位置上,它所帶來(lái)的好處是你難以想象的。
(3) 初始化列表
我們都知道,初始化列表一般是用來(lái)初始化const或者reference數(shù)據(jù)成員。但是由于他自身的性質(zhì),我們可以通過(guò)使用初始化列表來(lái)實(shí)現(xiàn)性能的提升。我們先來(lái)看一段程序:
class Person
{
private:
C c_1;
C c_2;
public:
Person(const C & c1, const C& c2 ): c_1(c1), c_2(c2) {}
};
當(dāng)然構(gòu)造函數(shù)我們也可以這樣寫:
Person::Person(const C& c1, const C& c2)
{
c_1 = c1;
c_2 = c2;
}
那么究竟二者會(huì)帶來(lái)什么樣的性能差異呢,要想搞清楚這個(gè)問(wèn)題,我們首先要搞清楚二者是如何執(zhí)行的,先來(lái)看初始化列表:數(shù)據(jù)成員的聲明操作都是在構(gòu)造函數(shù)執(zhí)行之前就完成了,在構(gòu)造函數(shù)中往往完成的只是賦值操作,然而初始化列表直接是在數(shù)據(jù)成員聲明的時(shí)候就進(jìn)行了初始化,因此它只執(zhí)行了一次copy constructor。再來(lái)看在構(gòu)造函數(shù)中賦值的情況:首先,在構(gòu)造函數(shù)執(zhí)行前會(huì)通過(guò)default constructor創(chuàng)建數(shù)據(jù)成員,然后在構(gòu)造函數(shù)中通過(guò)operator =進(jìn)行賦值。因此它就比初始化列表多進(jìn)行了一次函數(shù)調(diào)用。性能差異就出來(lái)了。但是請(qǐng)注意,如果你的數(shù)據(jù)成員都是基本類型的話,那么為了程序的可讀性就不要使用初始化列表了,因?yàn)榫幾g器對(duì)兩者產(chǎn)生的匯編代碼是相同的。
(4) postfix VS prefix 運(yùn)算符
prefix運(yùn)算符++和—比它的postfix版本效率更高,因?yàn)楫?dāng)postfix運(yùn)算符被使用的時(shí)候,會(huì)需要一個(gè)臨時(shí)對(duì)象來(lái)保存改變以前的值。對(duì)于基本類型,編譯器會(huì)消除這一份額外的拷貝,但是對(duì)于用戶定義類型,這似乎是不可能的。因此請(qǐng)你盡可能使用prefix運(yùn)算符。
三. 內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)函數(shù)既能夠去除函數(shù)調(diào)用所帶來(lái)的效率負(fù)擔(dān)又能夠保留一般函數(shù)的優(yōu)點(diǎn)。然而,內(nèi)聯(lián)函數(shù)并不是萬(wàn)能藥,在一些情況下,它甚至能夠降低程序的性能。因此在使用的時(shí)候應(yīng)該慎重。
1.我們先來(lái)看看內(nèi)聯(lián)函數(shù)給我們帶來(lái)的好處:從一個(gè)用戶的角度來(lái)看,內(nèi)聯(lián)函數(shù)看起來(lái)和普通函數(shù)一樣,它可以有參數(shù)和返回值,也可以有自己的作用域,然而它卻不會(huì)引入一般函數(shù)調(diào)用所帶來(lái)的負(fù)擔(dān)。另外,它可以比宏更安全更容易調(diào)試。
當(dāng)然有一點(diǎn)應(yīng)該意識(shí)到,inline specifier僅僅是對(duì)編譯器的建議,編譯器有權(quán)利忽略這個(gè)建議。那么編譯器是如何決定函數(shù)內(nèi)聯(lián)與否呢?一般情況下關(guān)鍵性因素包括函數(shù)體的大小,是否有局部對(duì)象被聲明,函數(shù)的復(fù)雜性等等。
2.那么如果一個(gè)函數(shù)被聲明為inline但是卻沒(méi)有被內(nèi)聯(lián)將會(huì)發(fā)生什么呢?理論上,當(dāng)編譯器拒絕內(nèi)聯(lián)一個(gè)函數(shù)的時(shí)候,那個(gè)函數(shù)會(huì)像普通函數(shù)一樣被對(duì)待,但是還會(huì)出現(xiàn)一些其他的問(wèn)題。例如下面這段代碼:
// filename Time.h
#include<ctime>
#include<iostream>
using namespace std;
class Time
{
public:
inline void Show() { for (int i = 0; i <10; i++) cout<<time(0)<<endl;}
};
因?yàn)槌蓡T函數(shù)Time::Show()包括一個(gè)局部變量和一個(gè)for循環(huán),所以編譯器一般拒絕inline,并且把它當(dāng)作一個(gè)普通的成員函數(shù)。但是這個(gè)包含類聲明的頭文件會(huì)被單獨(dú)的#include進(jìn)各個(gè)獨(dú)立的編譯單元中:
// filename f1.cpp
#include "Time.hj"
void f1()
{
Time t1;
t1.Show();
}
// filename f2.cpp
#include "Time.h"
void f2()
{
Time t2;
t2.Show();
}
結(jié)果編譯器為這個(gè)程序生成了兩個(gè)相同成員函數(shù)的拷貝:
void f1();
void f2();
int main()
{
f1();
f2();
return 0;
}
當(dāng)程序被鏈接的時(shí)候,linker將會(huì)面對(duì)兩個(gè)相同的Time::Show()拷貝,于是函數(shù)重定義的連接錯(cuò)誤發(fā)生。但是老一些的C++實(shí)現(xiàn)對(duì)付這種情況的辦法是通過(guò)把一個(gè)un-inlined函數(shù)當(dāng)作static來(lái)處理。因此每一份函數(shù)拷貝僅僅在自己的編譯單元中可見(jiàn),這樣鏈接錯(cuò)誤就解決了,但是在程序中卻會(huì)留下多份函數(shù)拷貝。在這種情況下,程序的性能不但沒(méi)有提升,反而增加了編譯和鏈接時(shí)間以及最終可執(zhí)行體的大小。
但是幸運(yùn)的是,新的C++標(biāo)準(zhǔn)中關(guān)于un-inlined函數(shù)的說(shuō)法已經(jīng)改變。一個(gè)符合標(biāo)準(zhǔn)C++實(shí)現(xiàn)應(yīng)該只生成一份函數(shù)拷貝。然而,要想所有的編譯器都支持這一點(diǎn)可能還需要很長(zhǎng)時(shí)間。
另外關(guān)于內(nèi)聯(lián)函數(shù)還有兩個(gè)更令人頭疼的問(wèn)題。第一個(gè)問(wèn)題是該如何進(jìn)行維護(hù)。一個(gè)函數(shù)開始的時(shí)候可能以內(nèi)聯(lián)的形式出現(xiàn),但是隨著系統(tǒng)的擴(kuò)展,函數(shù)體可能要求添加額外的功能,結(jié)果內(nèi)聯(lián)函數(shù)就變得不太可能,因此需要把inline specifier去除以及把函數(shù)體放到一個(gè)單獨(dú)的源文件中。另一個(gè)問(wèn)題是當(dāng)內(nèi)聯(lián)函數(shù)被應(yīng)用在代碼庫(kù)的時(shí)候產(chǎn)生。當(dāng)內(nèi)聯(lián)函數(shù)改變的時(shí)候,用戶必須重新編譯他們的代碼以反映這種改變。然而對(duì)于一個(gè)非內(nèi)聯(lián)函數(shù),用戶僅僅需要重新鏈接就可以了。
這里想要說(shuō)的是,內(nèi)聯(lián)函數(shù)并不是一個(gè)增強(qiáng)性能的靈丹妙藥。只有當(dāng)函數(shù)非常短小的時(shí)候它才能得到我們想要的效果,但是如果函數(shù)并不是很短而且在很多地方都被調(diào)用的話,那么將會(huì)使得可執(zhí)行體的體積增大。最令人煩惱的還是當(dāng)編譯器拒絕內(nèi)聯(lián)的時(shí)候。在老的實(shí)現(xiàn)中,結(jié)果很不盡人意,雖然在新的實(shí)現(xiàn)中有很大的改善,但是仍然還是不那么完善的。一些編譯器能夠足夠的聰明來(lái)指出哪些函數(shù)可以內(nèi)聯(lián)哪些不能,但是,大多數(shù)編譯器就不那么聰明了,因此這就需要我們的經(jīng)驗(yàn)來(lái)判斷。如果內(nèi)聯(lián)函數(shù)不能增強(qiáng)行能,就避免使用它!
四. 優(yōu)化你的內(nèi)存使用
通常優(yōu)化都有幾個(gè)方面:更快的運(yùn)行速度,有效的系統(tǒng)資源使用,更小的內(nèi)存使用。一般情況下,代碼優(yōu)化都是試圖在以上各個(gè)方面進(jìn)行改善。重新放置聲明技術(shù)被證明是消除多余對(duì)象的建立和銷毀,這樣既減小了程序的大小又加快了運(yùn)行速度。然而其他的優(yōu)化技術(shù)都是基于一個(gè)方面------更快的速度或者是更小的內(nèi)存使用。有時(shí),這些目標(biāo)是互斥的,壓縮了內(nèi)存的使用往往卻減慢了代碼速度,快速的代碼卻又需要更多的內(nèi)存支持。下面總結(jié)兩種在內(nèi)存使用上的優(yōu)化方法:
1. Bit Fields
在C/C++中都可以存取和訪問(wèn)數(shù)據(jù)的最小組成單元:bit。因?yàn)閎it并不是C/C++基本的存取單元,所以這里是通過(guò)犧牲運(yùn)行速度來(lái)減少內(nèi)存和輔助存儲(chǔ)器的空間的使用。注意:一些硬件結(jié)構(gòu)可能提供了特殊的處理器指令來(lái)存取bit,因此bit fields是否影響程序的速度取決于具體平臺(tái)。
在我們的現(xiàn)實(shí)生活中,一個(gè)數(shù)據(jù)的許多位都被浪費(fèi)了,因?yàn)槟承?yīng)用根本就不會(huì)有那么大的數(shù)據(jù)范圍。也許你會(huì)說(shuō),bit是如此之小,通過(guò)它就能減小存儲(chǔ)空間的使用嗎?的確,在數(shù)據(jù)量很小的情況下不會(huì)看出什么效果,但是在數(shù)據(jù)量驚人的情況下,它所節(jié)省的空間還是能夠讓我們的眼睛為之一亮的。也許你又會(huì)說(shuō),現(xiàn)在內(nèi)存和硬盤越來(lái)越便宜,何苦要費(fèi)半天勁,這省不了幾個(gè)錢。但是還有另外一個(gè)原因一定會(huì)使你信服,那就是數(shù)字信息傳輸。一個(gè)分布式數(shù)據(jù)庫(kù)都會(huì)在不同的地點(diǎn)有多份拷貝。那么數(shù)百萬(wàn)的紀(jì)錄傳輸就會(huì)顯得十分昂貴。Ok,現(xiàn)在我們就來(lái)看看該如何做吧,首先看下面這段代碼:
struct BillingRec
{
long cust_id;
long timestamp;
enum CallType
{
toll_free,
local,
regional,
long_distance,
international,
cellular
} type;
enum CallTariff
{
off_peak,
medium_rate,
peak_time
} tariff;
};
上面這個(gè)結(jié)構(gòu)體在32位的機(jī)器上將會(huì)占用16字節(jié),你會(huì)發(fā)現(xiàn)其中有許多位都被浪費(fèi)了,尤其是那兩個(gè)enum型,浪費(fèi)更是嚴(yán)重,所以請(qǐng)看下面做出的改進(jìn):
struct BillingRec
{
int cust_id: 24; // 23 bits + 1 sign bit
int timestamp: 24;
enum CallType
{//...
};
enum CallTariff
{//...
};
unsigned call: 3;
unsigned tariff: 2;
};
現(xiàn)在一個(gè)數(shù)據(jù)從16字節(jié)縮減到了8字節(jié),減少了一半,怎么樣,效果還是顯著的吧:)
2. Unions
Unions通過(guò)把兩個(gè)或更多的數(shù)據(jù)成員放置在相同地址的內(nèi)存中來(lái)減少內(nèi)存浪費(fèi),這就要求在任何時(shí)間只能有一個(gè)數(shù)據(jù)成員有效。Union 可以有成員函數(shù),包括構(gòu)造函數(shù)和析構(gòu)函數(shù),但是它不能有虛函數(shù)。C++支持anonymous unions。anonymous union是一個(gè)未命名類型的未命名對(duì)象。例如:
union { long n; void * p}; // anonymous
n = 1000L; // members are directly accessed
p = 0; // n is now also 0
不像命名的union,它不能有成員函數(shù)以及非public的數(shù)據(jù)成員。
那么unions什么時(shí)候是有用的呢?下面這個(gè)類從數(shù)據(jù)庫(kù)中獲取一個(gè)人的信息。關(guān)鍵字既可以是一個(gè)特有的ID或者人名,但是二者卻不能同時(shí)有效:
class PersonalDetails
{
private:
char * name;
long ID;
//...
public:
PersonalDetails(const char *nm); //key is of type char * used
PersonalDetails(long id) : ID(id) {} //numeric key used
};
上面這段代碼中就會(huì)造成內(nèi)存的浪費(fèi),因?yàn)樵谝粋€(gè)時(shí)間只能有一個(gè)關(guān)鍵字有效。anonymous union可以在這里使用來(lái)減少內(nèi)存的使用,例如:
class PersonalDetails
{
private:
union //anonymous
{
char * name;
long ID;
};
public:
PersonalDetails(const char *nm);
PersonalDetails(long id) : ID(id) {/**/} // direct access to a member
//...
};
通過(guò)使用union,PersonalDetails類的大小被減半。但是這里要說(shuō)明的是,節(jié)省4 個(gè)字節(jié)內(nèi)存并不值得引入union所帶來(lái)的麻煩,除非這個(gè)類作為數(shù)百萬(wàn)數(shù)據(jù)庫(kù)記錄的類型或者紀(jì)錄在一條很慢的通信線路傳輸。值得注意的是unions并不引入任何運(yùn)行期負(fù)擔(dān),所以這里不會(huì)有什么速度上的損失。anonymous union的優(yōu)點(diǎn)就是它的成員可以被直接訪問(wèn)。
五. 速度優(yōu)化
在一些對(duì)速度要求非常苛刻的應(yīng)用系統(tǒng)中,每一個(gè)CPU周期都是要爭(zhēng)取的。這個(gè)部分展現(xiàn)了一些簡(jiǎn)單方法來(lái)進(jìn)行速度優(yōu)化。
1. 使用類來(lái)包裹長(zhǎng)的參數(shù)列表
一個(gè)函數(shù)調(diào)用的負(fù)擔(dān)將會(huì)隨著參數(shù)列表的增長(zhǎng)而增加。運(yùn)行時(shí)系統(tǒng)不得不建立堆棧來(lái)存儲(chǔ)參數(shù)值;通常,當(dāng)參數(shù)很多的時(shí)候,這樣一個(gè)操作就會(huì)花費(fèi)很長(zhǎng)的時(shí)間。
把參數(shù)列表包裹進(jìn)一個(gè)單獨(dú)的類中并且通過(guò)引用進(jìn)行傳遞,這樣將會(huì)節(jié)省很多的時(shí)間。當(dāng)然,如果函數(shù)本身就很長(zhǎng),那么建立堆棧的時(shí)間就可以忽略了,因此也就沒(méi)有必要這樣做。然而,對(duì)于那些執(zhí)行時(shí)間很短而且經(jīng)常被調(diào)用的函數(shù)來(lái)說(shuō),包裹一個(gè)長(zhǎng)的參數(shù)列表在對(duì)象中并且通過(guò)引用傳遞將會(huì)提高性能。
2. 寄存器變量
register specifier被用來(lái)告訴編譯器一個(gè)對(duì)象將被會(huì)非常多的使用,可以把它放入寄存器中。例如:
void f()
{
int *p = new int[3000000];
register int *p2 = p; //store the address in a register
for (register int j = 0; j <3000000; j++)
{
*p2++ = 0;
}
//...use p
delete [] p;
}
循環(huán)計(jì)數(shù)是應(yīng)用寄存器變量的最好的候選者。當(dāng)它們沒(méi)有被存入一個(gè)寄存器中,大部分的循環(huán)時(shí)間都被用在了從內(nèi)存中取出變量和給變量賦新值上。如果把它存入一個(gè)寄存器中的話,將會(huì)大大減少這種負(fù)擔(dān)。需要注意的是,register specifier僅僅是對(duì)編譯器的一個(gè)建議。就好比內(nèi)聯(lián)函數(shù)一樣,編譯器可以拒絕把一個(gè)對(duì)象存儲(chǔ)到寄存器中。另外,現(xiàn)代的編譯器都會(huì)通過(guò)把變量放入寄存器中來(lái)優(yōu)化循環(huán)計(jì)數(shù)。Register storage specifier并不僅僅局限在基本類型上,它能夠被應(yīng)用于任何類型的對(duì)象。如果對(duì)象太大而不能裝進(jìn)寄存器的話,編譯器仍然能夠把它放入一個(gè)高速存儲(chǔ)器中,例如cache。
用register storage specifier聲明函數(shù)型參將會(huì)是建議編譯器把實(shí)參存入寄存器中而不是堆棧中。例如:
void f(register int j, register Date d);
3. 把那些保持不變的對(duì)象聲明為const
通過(guò)把對(duì)象聲明為const,編譯器就可以利用這個(gè)聲明把這樣一個(gè)對(duì)象放入寄存器中。
4. Virtual function的運(yùn)行期負(fù)擔(dān)
當(dāng)調(diào)用一個(gè)virtual function,如果編譯器能夠解決調(diào)用的靜態(tài)化,將不會(huì)引入額外的負(fù)擔(dān)。另外,一個(gè)非常短的虛函數(shù)可以被內(nèi)聯(lián)處理。在下面這個(gè)例子中,一個(gè)聰明的編譯器能夠做到靜態(tài)調(diào)用虛函數(shù):
#include <iostream>
using namespace std;
class V
{
public:
virtual void show() const { cout <<"I"m V"<<endl; }
};
class W : public V
{
public:
void show() const { cout <<"I"m W"<<endl; }
};
void f(V & v, V *pV)
{
v.show();
pV- >show();
}
void g()
{
V v;
f(v, &v);
}
int main()
{
g();
return 0;
}
如果整個(gè)程序出現(xiàn)在一個(gè)單獨(dú)的編譯單元中,編譯器能夠?qū)ain()中的g()進(jìn)行內(nèi)聯(lián)替換。并且在g()中f()的調(diào)用也能夠被內(nèi)聯(lián)處理。因?yàn)閭鹘of()的參數(shù)的動(dòng)態(tài)類型能夠在編譯期被知曉,因此編譯器能夠把對(duì)虛函數(shù)的調(diào)用靜態(tài)化。但是不能保證每個(gè)編譯器都這樣做。然而,一些編譯器確實(shí)能夠利用在編譯期獲得參數(shù)的動(dòng)態(tài)類型從而使得函數(shù)的調(diào)用在編譯期間就確定了下來(lái),避免了動(dòng)態(tài)綁定的負(fù)擔(dān)。
5. Function objects VS function pointers
用function objects取代function pointers的好處不僅僅局限在能夠泛化和簡(jiǎn)單的維護(hù)性上。而且編譯器能夠?qū)unction object的函數(shù)調(diào)用進(jìn)行內(nèi)聯(lián)處理,從而進(jìn)一步的增強(qiáng)了性能
六. 最后的求助
迄今為止為大家展示的優(yōu)化技術(shù)并沒(méi)有在設(shè)計(jì)以及代碼的可讀性上做出妥協(xié)。事實(shí)上,它們中的一些還提高了軟件的穩(wěn)固性和可維護(hù)性。但是在一些對(duì)時(shí)間和內(nèi)存有嚴(yán)格限制的軟件開發(fā)中,上面的技術(shù)可能還不夠;有可能還需要一些會(huì)影響軟件的可移植性和擴(kuò)展性的技術(shù)。但是這些技術(shù)只能在所有其他的優(yōu)化技術(shù)都被應(yīng)用但是還不符合要求的情況下使用。
1. 關(guān)閉RTTI和異常處理支持
當(dāng)你導(dǎo)入純C代碼給C++編譯器的時(shí)候,你可能會(huì)發(fā)現(xiàn)有一些性能上的損失。這并不是語(yǔ)言或者編譯器的錯(cuò)誤,而是編譯器作出的一些調(diào)整。如果你想獲得和C編譯器同樣的性能,那么請(qǐng)關(guān)閉編譯器對(duì)RTTI以及異常處理的支持。為什么會(huì)這樣呢?因?yàn)闉榱酥С諶TTI和異常處理,C++編譯器會(huì)插入額外的代碼。這樣就增加了可執(zhí)行體的大小,從而使得效率有所下降。當(dāng)應(yīng)用純C代碼的時(shí)候,那些額外的代碼是不需要的,所以你可以通過(guò)關(guān)閉來(lái)避免它。
2. 內(nèi)聯(lián)匯編
對(duì)時(shí)間要求苛刻的部分可以用本地匯編來(lái)重寫。結(jié)果可能是速度上的顯著提高。然而,這個(gè)方法不能想當(dāng)然的就去實(shí)施,因?yàn)樗鼘⑹沟脤?lái)的修改非常的困難。維護(hù)代碼的程序員可能對(duì)匯編并不了解。如果想要把軟件運(yùn)行于其他平臺(tái)也需要重寫匯編代碼部分。另外,開發(fā)和測(cè)試匯編代碼是一件辛苦的工作,它將花費(fèi)更長(zhǎng)的時(shí)間。
3. 直接和操作系統(tǒng)進(jìn)行交互
API函數(shù)可以使你直接與操作系統(tǒng)進(jìn)行交互。有時(shí),直接執(zhí)行一個(gè)系統(tǒng)命令可能會(huì)快許多。出于這個(gè)目的,你可以使用標(biāo)準(zhǔn)函數(shù)system()。例如,在一個(gè)dos/windows系統(tǒng)下,你可以這樣顯示當(dāng)前目錄下的文件:
#include <cstdlib>
using namespace std;
int main()
{
system( "dir"); //execute the "dir" command
}
注意:這里是在速度和可移植性以及可擴(kuò)展性之間做出的折衷。
5.?
代碼優(yōu)化
所謂代碼優(yōu)化是指對(duì)程序代碼進(jìn)行等價(jià)(指不改變程序的運(yùn)行結(jié)果)變換。程序代碼可以是中間代碼(如四元式代碼),也可以是目標(biāo)代碼。等價(jià)的含義是使得變換后的代碼運(yùn)行結(jié)果與變換前代碼運(yùn)行結(jié)果相同。優(yōu)化的含義是最終生成的目標(biāo)代碼短(運(yùn)行時(shí)間更短、占用空間更小),時(shí)空效率優(yōu)化。原則上,優(yōu)化可以再編譯的各個(gè)階段進(jìn)行,但最主要的一類是對(duì)中間代碼進(jìn)行優(yōu)化,這類優(yōu)化不依賴于具體的計(jì)算機(jī)。
目錄
編輯本段分類
編譯過(guò)程中可進(jìn)行的優(yōu)化可按階段劃分:優(yōu)化可在編譯的不同階段進(jìn)行,分為中間代碼一級(jí)和目標(biāo)代碼一級(jí)的優(yōu)化。可按優(yōu)化涉及的程序范圍劃分:對(duì)同一階段,分為局部?jī)?yōu)化,循環(huán)優(yōu)化和全局優(yōu)化. 進(jìn)行優(yōu)化所需要的基礎(chǔ)是對(duì)代碼進(jìn)行數(shù)據(jù)流分析和控制流分析。如劃分 DAG,查找循環(huán),分析變量的定值點(diǎn)和引用點(diǎn)等等。最常用的代碼優(yōu)化技術(shù)有刪除多余運(yùn)算,循環(huán)不變代碼外提,強(qiáng)度削弱,變換循環(huán)控制條件,合并已知量與復(fù)寫傳播,以及刪除無(wú)用賦值等等。編輯本段要點(diǎn)
一. 盡量采用 div+css布局您的頁(yè)面,div+css布局的好處是讓 搜索引擎爬蟲能夠更順利的,更快的,更友好的爬完您的頁(yè)面;div+css布局還可以大量縮減網(wǎng)頁(yè)大小,使得代碼更簡(jiǎn)潔,流暢,更容易放置更多內(nèi)容。 二. 盡量縮減您的頁(yè)面大小,因?yàn)樗阉饕媾老x每次爬行您的站點(diǎn)時(shí),存儲(chǔ)數(shù)據(jù)的容量有限,一般建議100 KB以下,越小越好,但不能小于5KB。網(wǎng)頁(yè)大小減少還有一個(gè)好處,能夠促使您的站點(diǎn)形成巨大的內(nèi)部鏈接網(wǎng)。 三. 盡量少用無(wú)用的圖片和flash。內(nèi)容索引所派出的搜索引擎爬蟲,不認(rèn)識(shí)圖片,只能根據(jù)圖片“ ALT, TITLE”等屬性的內(nèi)容判斷圖片的內(nèi)容。對(duì)于 flash搜索引擎爬蟲更是視而不見(jiàn)。 四. 盡量滿足w3c標(biāo)準(zhǔn),網(wǎng)頁(yè)代碼的編寫滿足W3C標(biāo)準(zhǔn),能夠提升網(wǎng)站和搜索引擎的友好度,因?yàn)樗阉饕媸珍洏?biāo)準(zhǔn),排名算法,都是在 W3C標(biāo)準(zhǔn)的基礎(chǔ)上開發(fā)的。 五. 盡量更深層次套用標(biāo)簽 h1、h2、h3、h4、h5…..,讓搜索引擎能夠分辨清晰網(wǎng)頁(yè)那一塊很重要,那一塊次之。 六. 盡量少用 JS,JS代碼全部用外部調(diào)用文件封裝。搜索引擎不喜歡JS,影響網(wǎng)站的友好度指數(shù)。 七. 盡量不使用表格布局,因?yàn)樗阉饕鎸?duì)表格布局嵌套3層以內(nèi)的內(nèi)容懶的去抓取。搜索引擎爬蟲有時(shí)候也是比較懶的,望各位一定要保持代碼和內(nèi)容在3層以內(nèi)。 八. 盡量不讓CSS分散在HTML標(biāo)記里,盡量封裝到外部調(diào)用文件。如果CSS出現(xiàn)在 HTML標(biāo)記里,搜索引擎爬蟲就要分散注意力去關(guān)注這些對(duì)優(yōu)化沒(méi)有任何意義的東西,所以建議封裝到專用CSS文件中。 九.清理垃圾代碼,要把代碼編輯環(huán)境下敲擊鍵盤上的空格鍵所產(chǎn)生的符號(hào);把一些默認(rèn)屬性代碼,不會(huì)影響顯示的代碼;注釋語(yǔ)句如果對(duì)代碼可讀性沒(méi)有太大影響,清理這些垃圾代碼,會(huì)減少不少的空間。創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)
總結(jié)
以上是生活随笔為你收集整理的代码 优化 指南 实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android Studio更新成2.3
- 下一篇: IP地址不是唯一的吗?为什么路由器的IP