《深入理解計(jì)算機(jī)系統(tǒng)》| 優(yōu)化程序的性能

編寫運(yùn)行的快的程序有三個(gè)因素:①選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)揽涮;②理解編譯器的能力,使用有效的方式讓編譯器能進(jìn)行優(yōu)化搭儒;③對(duì)于運(yùn)算量特別大的程序立美,可能還需要進(jìn)行任務(wù)分解政恍。在這一過(guò)程中可能還需要對(duì)程序的可讀性和運(yùn)行速度進(jìn)行權(quán)衡。

在閱讀這一章節(jié)的過(guò)程中花費(fèi)了大量的時(shí)間對(duì)我自己的自動(dòng)辦公軟件進(jìn)行了優(yōu)化雪标,算是學(xué)以致用。選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)不在本章的講解內(nèi)容中溉跃,我們從編譯器的能力和局限性講起著重介紹幾種提高程序運(yùn)行速度的方法

1.1 編譯器的局限性


編譯器遵循的一個(gè)優(yōu)化程序的原則是:安全優(yōu)化村刨,也就是說(shuō)為了保證程序的正常運(yùn)行(優(yōu)化后的版本與未優(yōu)化的版本有一致的行為,這不是廢話嗎)編譯器一般都是很保守的撰茎。來(lái)看一個(gè)例子:

安全優(yōu)化:存儲(chǔ)器別名

從上面的例子不能看出嵌牺,一般情況下twiddle2要求三次存儲(chǔ)器引用(讀*xp,讀*yp龄糊,寫*xp)而中twiddle1要求六次存儲(chǔ)器引用(2次讀*xp逆粹,2次讀*yp,2次寫*xp)炫惩。所以twiddle2的效率要高于twiddle1僻弹,但是如果考慮到xp等于yp,指向存儲(chǔ)器中的同一個(gè)位置的時(shí)候他嚷,我們用twiddle2來(lái)優(yōu)化twiddle1的版本就會(huì)造成程序的運(yùn)行結(jié)果的不同蹋绽。比如當(dāng)xp = yp = 2的時(shí)候,f(twiddle1) = 8 筋蓖;而f(twiddle2) = ?6 這就是我們說(shuō)的編譯器的局限性卸耘。

1.2 表示程序的性能CPE


在繼續(xù)介紹優(yōu)化大法的時(shí)候,我們對(duì)提高程序的性能做一個(gè)量化的參考扭勉,在以后的章節(jié)中好對(duì)比我們的優(yōu)化后版本的執(zhí)行效率鹊奖。

CPE:每元素周期(Cycles Per Element),使用時(shí)鐘周期涂炎,度量每隔周期執(zhí)行了多少條指令忠聚。通常當(dāng)一個(gè)標(biāo)有“4GHz”的處理器,這表示的是處理器時(shí)鐘運(yùn)行頻率4X10的9次方Hz每秒唱捣,那么一個(gè)時(shí)鐘周期就是時(shí)鐘頻率的倒數(shù)两蟀,為0.25納秒。

我們來(lái)看一個(gè)計(jì)算集合值的兩個(gè)函數(shù)震缭,我們假設(shè)有集合a = {1,2,4,5,7,9,10,12,16}集合p為集合a的前置和也就是p={1, 1+2, 1+2+4, 1+2+4+5,1+2+4+5+7赂毯,……1+2+4+5+7+9+10+12+16}

我們有兩種計(jì)算前置和p的方式,psum1和psum2:

循環(huán)展開技術(shù)

psum1是我們通常用到的版本,看起來(lái)也比較順眼党涕,psum2是我們以后要詳細(xì)講解的循環(huán)展開技術(shù)烦感,核心的思想就是每次循環(huán)計(jì)算兩個(gè)元素p[i]和p[i+1]從而減少了循環(huán)的次數(shù)。這個(gè)內(nèi)容我們以后講解膛堤。

來(lái)看一下兩個(gè)函數(shù)的性能對(duì)比手趣,數(shù)據(jù)說(shuō)話:

x軸表示處理的元素,y軸表示周期

我們可以很明顯的看出來(lái)肥荔,當(dāng)處理的數(shù)據(jù)量小的時(shí)候绿渣,兩個(gè)版本的區(qū)別不大,但當(dāng)周期在1000以上的時(shí)候燕耿,能處理元素的個(gè)數(shù)就明顯不同了而且這種趨勢(shì)越拉越大中符。

1.3優(yōu)化大法好:一個(gè)程序的進(jìn)化過(guò)程


智人的進(jìn)化過(guò)程

從大約7萬(wàn)年前的認(rèn)知革命開始,智人的進(jìn)化經(jīng)歷了漫長(zhǎng)的過(guò)程誉帅,終于實(shí)現(xiàn)了從動(dòng)物到“上帝”的轉(zhuǎn)變淀散,我們將從一個(gè)簡(jiǎn)單的程序示例講起帶領(lǐng)大家一步步實(shí)現(xiàn)這個(gè)過(guò)程,當(dāng)然不會(huì)花費(fèi)上萬(wàn)年的時(shí)間堵第。

① 原始版本:程序示例

計(jì)算一個(gè)向量的集合

有必要解釋一個(gè)combine1函數(shù)的作用:計(jì)算一個(gè)向量的集合

我們的向量有如下數(shù)據(jù)結(jié)構(gòu):

向量由頭信息加上指定長(zhǎng)度的數(shù)組表示

我們定義typedef int data_t吧凉,方便我們用data_t表示不同的int、float踏志、doubule數(shù)據(jù)阀捅。

我們使用: #define IDENT 0 和 #define OP ? +來(lái)對(duì)不同的運(yùn)算進(jìn)行求值,其中OP代表運(yùn)算符號(hào)针余,而IDENT代表不同的初始值饲鄙。

好了,作為一個(gè)起點(diǎn)圆雁,我們來(lái)看看我們的黑猩猩版本:combine1的效率:

未優(yōu)化版本的效率

的確有點(diǎn)兒慘不忍睹忍级,我們能為他做些什么呢?開始來(lái)進(jìn)行 一些改進(jìn)吧
② 代碼移動(dòng):消除循環(huán)的低效率

改進(jìn)循環(huán)的效率:將vec_length移除循環(huán)外

一個(gè)看上去無(wú)足輕重的代碼片段可能隱藏有漸近低效率伪朽,上面combine2只是將求得向量長(zhǎng)度的vec_length移除了循環(huán)外轴咱,因?yàn)橄蛄康拈L(zhǎng)度不會(huì)隨著循環(huán)的進(jìn)行而改變。我們來(lái)看看性能的改變:

③ 減少函數(shù)的調(diào)用

分析:combine2的代碼可以看出烈涮,在循環(huán)的過(guò)程中每次都會(huì)調(diào)用get_vec_element來(lái)訪問向量的元素朴肺,對(duì)于數(shù)組的引用,檢查邊界是合理的坚洽,但分析我們向量的數(shù)據(jù)結(jié)構(gòu)不難看出戈稿,不進(jìn)行邊界檢查我們也能夠進(jìn)行合法的訪問:

消除循環(huán)中的函數(shù)調(diào)用

就像《葵花寶典》開篇就講到的內(nèi)容,欲練此功揮刀自宮讶舰,當(dāng)我們?cè)谶M(jìn)行循環(huán)體內(nèi)的調(diào)用函數(shù)優(yōu)化的時(shí)候鞍盗,必然會(huì)損害一些程序的模塊性需了,慎用!

④ 消除不必要的存儲(chǔ)器引用

分析:combine3中每次合并計(jì)算會(huì)將值累計(jì)在指針dest指定的位置上般甲,我們來(lái)看看匯編代碼:

rbp保存dest的值

從以上匯編代碼中我們看出肋乍,dest的值存放在rbp中,每次循環(huán)敷存,要先讀rbp到xmm0住拭,計(jì)算后的結(jié)果又會(huì)重新寫入到rbp中去,這樣寫很浪費(fèi)历帚。我們能夠消除這樣不合理的引用:

臨時(shí)局部變量acc存放中間結(jié)果

我們使用局部變量acc保存累計(jì)計(jì)算的結(jié)果,這樣就消除了每次循環(huán)都要對(duì)存儲(chǔ)器進(jìn)行取值和寫回杠娱,使得程序性能有顯著的提高挽牢。

1.4 理解現(xiàn)代處理器分析combine4的效率瓶頸


到目前為止的優(yōu)化都不依賴于機(jī)器的特性,我們將學(xué)習(xí)現(xiàn)代處理器的一些知識(shí)摊求,比如:關(guān)鍵路徑禽拔、延遲界限以達(dá)到處理器級(jí)別的優(yōu)化。

上圖是一個(gè)簡(jiǎn)易的處理器框架圖室叉,在實(shí)際的處理中睹栖,處理器同時(shí)對(duì)多條指令求值(指令級(jí)并行),同時(shí)又呈現(xiàn)出一種簡(jiǎn)單的順序執(zhí)行的現(xiàn)象茧痕。 整個(gè)框架分為兩個(gè)大部分:指令控制單元(ICU)和指令執(zhí)行單元(EU)野来,前者負(fù)責(zé)從存儲(chǔ)器中讀出指令序列,生成對(duì)數(shù)據(jù)的基本操作踪旷。后者負(fù)責(zé)執(zhí)行曼氛。我們分別進(jìn)行講解:

指令控制單元(ICU):ICU從指令高速緩存(包含最近訪問的指令)中讀取指令,通常在ICU當(dāng)前執(zhí)行指令很早之前就開始取指令野,譯碼并發(fā)送到EU單元執(zhí)行舀患。當(dāng)遇到了分支指令的時(shí)候:處理器采用分支預(yù)測(cè),投機(jī)執(zhí)行气破,在未確定該執(zhí)行哪些操作的時(shí)候就對(duì)不同的分支目標(biāo)地址進(jìn)行取值和譯碼甚至執(zhí)行聊浅。如果預(yù)測(cè)錯(cuò)誤就回到最初的位置。使用投機(jī)執(zhí)行技術(shù)求得的值不會(huì)存放在寄存器和數(shù)據(jù)存儲(chǔ)器中现使,寄存器中的退役單元控制著寄存器的更新低匙,只有當(dāng)所有的分支都確定是正確的時(shí)候,指令才會(huì)退役朴下,所有對(duì)寄存器的更新才會(huì)實(shí)際執(zhí)行努咐,否則清空該指令。

指令譯碼:將實(shí)際的指令轉(zhuǎn)化為一組基本的操作 addl %eax殴胧,4(%edx)轉(zhuǎn)為:①?gòu)拇鎯?chǔ)器中加載一個(gè)值到處理器中渗稍;②將加載的值加上eax佩迟;③重新寫回到存儲(chǔ)器中

指令執(zhí)行單元(EU):接受指令譯碼傳來(lái)的一組操作,然后分配到功能單元中竿屹,這些功能單元包括:分支报强、乘除、加法拱燃、加載和寫存儲(chǔ)器秉溉。其中對(duì)存儲(chǔ)器的訪問,通過(guò)加載和存儲(chǔ)功能單元對(duì)數(shù)據(jù)高速緩存的訪問來(lái)實(shí)現(xiàn)碗誉。

召嘶!寄存器重命名機(jī)制的實(shí)現(xiàn)方式:

先來(lái)看看什么是寄存器重命名:

圖1

指令4,5,6在功能上并不依賴于1,2,3的執(zhí)行,但是必須要等待1,2,3完成之后才能執(zhí)行4.

通過(guò)改變一下寄存器的名字可以解除限制:

圖2:將R1重命名為R2

實(shí)現(xiàn)方式:當(dāng)一條更新R1為R2指令譯碼時(shí)哮缺,將[r(R1)弄跌,t(R2)] 的對(duì)應(yīng)關(guān)系加入到一張表中,隨后當(dāng)圖1指令4需要再次訪問到R1的時(shí)候尝苇,發(fā)送到執(zhí)行單元的值會(huì)將R2作為操作數(shù)源的值铛只,而當(dāng)M[2048]完成賦值任務(wù)以后,會(huì)形成(v糠溜,t)的結(jié)果淳玩,指明標(biāo)記的結(jié)果M[2048]。所有等待R2的值都會(huì)使用v作為源值轉(zhuǎn)發(fā)非竿。這樣做的好處就是值可以從一個(gè)操作直接轉(zhuǎn)發(fā)到另一個(gè)操作蜕着,而不是寫到寄存器文件再讀出來(lái)。

我們學(xué)習(xí)了一些基礎(chǔ)的知識(shí)汽馋,我們重新來(lái)分析一下combine4的一些特性:

combine4:瓶頸在循環(huán)部分
已float的乘法為例

我們所熟悉的指令譯碼器侮东,會(huì)將以上這4條指令擴(kuò)展為5個(gè)步驟:

combine4的循環(huán)代碼圖形化表示

我們簡(jiǎn)單的來(lái)理解一下mulss指令的兩個(gè)操作:load指令加載rax、rdx的值并將計(jì)算的結(jié)果直接傳入到mul指令中豹芯,與xmm0進(jìn)行乘法運(yùn)算并將結(jié)果寫入到xmm0中悄雅。

我們重新畫一下上圖的內(nèi)容,使得結(jié)果看起來(lái)更清晰一些:

圖b中我們刪除了白色區(qū)域(無(wú)相關(guān)項(xiàng))和沒有修改寄存器的部分铁蹈,只留下了循環(huán)執(zhí)行過(guò)程中對(duì)xmm0和rdx迭代進(jìn)行的一系列操作宽闲。

關(guān)鍵路徑

終結(jié)一下:我們可以看出,兩大關(guān)鍵鏈條分別是:mul對(duì)acc的操作握牧,和add對(duì)i的操作容诬,而左邊的mul鏈條會(huì)成為關(guān)鍵路徑。通過(guò)對(duì)處理器結(jié)構(gòu)的分析沿腰,我們接下來(lái)不難看出览徒,要再一步進(jìn)行優(yōu)化,就只有對(duì)關(guān)鍵路徑進(jìn)行優(yōu)化了颂龙。(繼續(xù)講解循環(huán)展開技術(shù))

1.5 循環(huán)展開:一場(chǎng)真正意義上的進(jìn)化(combine5)


循環(huán)展開能減少循環(huán)開銷的影響

還是以我們之前講到的向量為例:

我們假設(shè)有集合a = {1,2,4,5,7,9,10,12,16}使用combine函數(shù)進(jìn)行求和運(yùn)算:我們模擬計(jì)算機(jī)的執(zhí)行順序习蓬,一步步在草圖上分析combine5代碼實(shí)現(xiàn)的功能纽什。

k=2循環(huán)展開兩次,字太丑見諒

總結(jié):我們之所以將limit定義為length-1躲叼,是向量的長(zhǎng)度不一定是2的倍數(shù)芦缰,如上圖中的9,為了不至于在第一次循環(huán)中越界訪問枫慷,我們將limit設(shè)置為length-1.雖然我們用到了兩次循環(huán)让蕾,但循環(huán)展開大大縮短的關(guān)鍵路徑,提高了效率或听。我們將這個(gè)思想歸納為循環(huán)展開k次探孝,k < length + 1 ,我們上面講的內(nèi)容就是k=2次誉裆,一次計(jì)算2個(gè)元素的和再姑。我們看看效率的提升:

浮點(diǎn)運(yùn)算無(wú)變化

注:為什么浮點(diǎn)運(yùn)算的沒有性能的提升?雖然展開了兩次循環(huán)找御,但是必須要順序的執(zhí)行,所以沒有性能的提升绍填。

兩次運(yùn)算展開后是順序執(zhí)行的

1.6 進(jìn)一步優(yōu)化:提高并行性(combine6霎桅、combine7)


分析:我們將累積變量放在一個(gè)單獨(dú)的acc中,在前面的計(jì)算完成前讨永,不能計(jì)算新的acc值

兩次循環(huán)展開滔驶,使用兩路并行

性能對(duì)比圖:

怎樣理解combine6帶來(lái)的性能提升:

我們看到唯一不同的地方在與循環(huán)①(標(biāo)號(hào)12)處代碼中,加入兩個(gè)累積變量:acc0和acc1卿闹,這樣做有什么好處呢揭糕?

acc = (acc OP data[i]) OP data[i+1]; ? 轉(zhuǎn)變?yōu)椋?/b>

acc0 = acc0 OP data[i]; ? 和 ?acc1 = acc1 OP data[i+1];

帶入分析圖(combine6)

我們?cè)賮?lái)看看圖形化的數(shù)據(jù)分析:

引入新變量acc0和acc1分配到不同寄存器寄存器xmm0和xmm1

這樣一來(lái)關(guān)鍵路徑就成了兩路并行,效率大大提升了:

cimbine6的關(guān)鍵路徑

還有沒有其他方法能打破順序相關(guān)而提高效率锻霎?來(lái)看看combine7變種:

combine7重新結(jié)合變換

標(biāo)號(hào)12的語(yǔ)句中與combine5相比著角,只是結(jié)合方式發(fā)生了變化,將:

acc = (acc OP data[i]) OP data[i+1] ?變成了 acc = acc OP (data[i] OP data[i+1])

我們來(lái)對(duì)比一下combine5和combine7兩個(gè)版本的數(shù)據(jù)流圖:

數(shù)據(jù)流圖對(duì)比

在combine7的版本中旋恼,第一個(gè)mul通過(guò)兩個(gè)load指令將i和i+1的乘機(jī)計(jì)算出來(lái)吏口,然后交給第二個(gè)mul將乘積累積到xmm1(acc)中。就不像combine5中的load mul順序執(zhí)行冰更,必須等到第一個(gè)load mul執(zhí)行完成以后才能進(jìn)行第二次load和mul操作产徊。我們將combine7的模型復(fù)制幾次就能看的關(guān)鍵路徑變成了n/2個(gè)操作,這就帶來(lái)了性能的提升:

combine7:我們看到只有一條關(guān)鍵路徑蜀细,而且包含n/2個(gè)操作

總結(jié):到目前為止舟铜,我們已經(jīng)完成了從combine1到combine7的進(jìn)化,帶來(lái)了至少10倍以上的效率提高奠衔,我們發(fā)現(xiàn)循環(huán)展開谆刨、并行累積值在多個(gè)變量中塘娶,是可靠的提高程序性能的方法。那還有那些限制因素呢制約著程序的性能呢痴荐?

一些限制因素:

① 寄存器溢出:當(dāng)我們的并行度超過(guò)了可用的寄存器數(shù)量血柳,編譯器就會(huì)將結(jié)果溢出到棧中,性能就會(huì)急劇下降生兆,筆記訪問存儲(chǔ)器的時(shí)間要長(zhǎng)很多难捌。

② 避免分支預(yù)測(cè)和預(yù)測(cè)錯(cuò)誤處罰:1> 不要過(guò)分關(guān)心可預(yù)測(cè)的分支,因?yàn)閹?lái)的性能差異很醒荒选根吁;2> 書寫適合用條件傳送實(shí)現(xiàn)的代碼。

1.7理解存儲(chǔ)器性能


分析:為什么要理解存儲(chǔ)器的性能合蔽?

當(dāng)我們要處理的數(shù)據(jù)小于1000個(gè)元素的向量击敌,數(shù)據(jù)量不會(huì)超過(guò)8000個(gè)字節(jié),這些內(nèi)容都會(huì)存放在多個(gè)高速緩存存儲(chǔ)器中拴事,已方便我們快速訪問沃斤。接下來(lái)我們會(huì)研究在高速緩存中的加載和存儲(chǔ)操作對(duì)性能的影響,充分利用高速緩存來(lái)編寫高效率的代碼

加載的性能:

我們?cè)趯?duì)鏈表的訪問中刃宵,可以看出加載函數(shù)對(duì)性能的影響衡瓶,舉個(gè)例子:

加載操作的延遲

我們來(lái)看看ls = ls->next這句的匯編代碼:

movq是這個(gè)循環(huán)的關(guān)鍵瓶頸

在標(biāo)號(hào)3中,使用movq指令牲证,加載值到rdi寄存器中哮针,而加載操作又依賴于rdi來(lái)計(jì)算加載的位置,也就是說(shuō)坦袍,必須要等到前一次加載完成才能進(jìn)行下一次循環(huán)十厢。這個(gè)函數(shù)的CPE等于4也就是說(shuō)加載的延遲為4.

存儲(chǔ)的性能:

分析:從理論上來(lái)講,存儲(chǔ)操作并不影響任何寄存器的值捂齐,不會(huì)產(chǎn)生任何數(shù)據(jù)相關(guān)蛮放。而只有加載操作是受存儲(chǔ)的影響的,因?yàn)橹挥屑虞d操作讀取的是有存儲(chǔ)器寫操作的值奠宜。

寫讀的相關(guān)性:

寫讀相關(guān)

為了討論寫和讀的相關(guān)性筛武,我們來(lái)看看上述代碼的兩種不同的情況:假設(shè)a[0]=-10,a[1]=17:

互不相干的情況CPE=2

舉例A中可以看出,初始化的條件下a{-10挎塌,17}徘六,val = 0, cnt = 3榴都;當(dāng)程序開始執(zhí)行循環(huán)的時(shí)候待锈,寫操作:*dest = val 而讀操作:val = (*src)+1訪問的分別是不同位置,a[0]和a[1]嘴高。就是我們前面說(shuō)過(guò)的數(shù)據(jù)不相關(guān)竿音。

讀寫相關(guān)CPE=6

而舉例B的情況就完全不一樣了和屎,dest和src操作的是同一塊位置a[0],一個(gè)存儲(chǔ)器讀的結(jié)果依賴于一個(gè)最近的存儲(chǔ)器的寫春瞬。為什么讀寫相關(guān)以后程序的性能就降低了柴信?我們來(lái)看看加載和存儲(chǔ)單元的內(nèi)部構(gòu)造:

加載和存儲(chǔ)單元的細(xì)節(jié)

在上圖的內(nèi)部構(gòu)造中,我們發(fā)現(xiàn)存儲(chǔ)單元多了一個(gè)存儲(chǔ)緩沖區(qū)宽气,這樣做有一個(gè)好處就是随常,當(dāng)一些列存儲(chǔ)操作開始執(zhí)行的時(shí)候,不需要等待高速緩存更新完成就能夠開始執(zhí)行了萄涯。而當(dāng)一條加載操作發(fā)生的時(shí)候绪氛,加載單元必須先檢查存儲(chǔ)緩沖區(qū),看看有沒有相匹配的條目涝影,如果匹配成功就從存儲(chǔ)緩沖區(qū)中取出數(shù)據(jù)作為加載的結(jié)果枣察。

內(nèi)循環(huán)數(shù)據(jù)流圖

上圖的內(nèi)容有點(diǎn)兒亂,我們來(lái)說(shuō)明一下:

①指令movl被譯碼成兩個(gè)操作:s_addr和s_data其中前者負(fù)責(zé)計(jì)算存儲(chǔ)器的地址燃逻,并在存儲(chǔ)緩沖區(qū)中創(chuàng)建一個(gè)條目序目,設(shè)置地址字段;后者負(fù)責(zé)設(shè)置該條目的數(shù)據(jù)字段伯襟;

②s_addr同s_data右邊的箭頭表示宛琅,設(shè)置數(shù)據(jù)字段必須要等到計(jì)算地址階段完成才能進(jìn)行。此外逗旁,第二條movl指令被譯碼成了load指令,這條加載指令必須要檢查所有的地址舆瘪,包括正在讀寫的地址片效,所以s_addr與這條load指令也有相關(guān)性;還有一條虛線相關(guān)性英古,連接s_data和load淀衣,這表示如果兩個(gè)地址相同,那么load必須要等到s_data將數(shù)據(jù)段設(shè)置到存儲(chǔ)緩沖區(qū)召调。我們將上圖修改一下膨桥,大家容易理解:

讀存數(shù)據(jù)相關(guān)流圖

標(biāo)號(hào)①表示:存儲(chǔ)地址必須在數(shù)據(jù)被存儲(chǔ)之前計(jì)算出來(lái);

標(biāo)號(hào)②表示:load操作將它的地址與所有未完成操作的地址進(jìn)行比較唠叛;

標(biāo)號(hào)③表示:數(shù)據(jù)相關(guān)只嚣,當(dāng)訪問相同位置時(shí)出現(xiàn)

我們剔除不影響數(shù)據(jù)操作的關(guān)聯(lián)后形成如下圖:

讀寫相關(guān)

我們清楚的看到了兩條的關(guān)鍵路徑,其中左邊表示的是:存儲(chǔ)加載相關(guān)艺沼;右邊表示的是增加數(shù)據(jù)值相關(guān)册舞。將以上圖復(fù)制幾次,并同不相關(guān)的數(shù)據(jù)進(jìn)行比較障般,我們能看到讀寫相關(guān)對(duì)CPE的影響了:

左邊地址不同调鲸,右邊相同

總結(jié):對(duì)存儲(chǔ)器的操作盛杰,只有當(dāng)加載和存儲(chǔ)地址都被計(jì)算出來(lái)了以后才能確定其對(duì)性能的影響。

1.8 優(yōu)化程序大法總結(jié)


到目前為止藐石,我們基本上上講完了所有的優(yōu)化程序性能的方法即供,套路如下:

① 高級(jí)設(shè)計(jì):選擇適當(dāng)?shù)乃惴ê蛿?shù)據(jù)結(jié)構(gòu),要提高警惕于微,避免漸近低效率逗嫡;

② 基本編碼原則:

*消除連續(xù)的函數(shù)調(diào)用,有可能的時(shí)候?qū)⒂?jì)算移動(dòng)到循環(huán)體外角雷;

*消除不必要的存儲(chǔ)器引用祸穷,引入臨時(shí)變量保存中間值。

③ 低級(jí)優(yōu)化:

*展開循環(huán)勺三,降低開銷雷滚;

*提高并行,使用多個(gè)累積變量或者重新結(jié)合吗坚,用良好的風(fēng)格重新條件操作祈远。

在實(shí)際的優(yōu)化程序的過(guò)程中,我們不可能像之前講到的簡(jiǎn)單程序那樣商源,快速的分析出一個(gè)程序片段的性能瓶頸车份,畢竟在真實(shí)的項(xiàng)目中源碼的量相當(dāng)大,這時(shí)候我們有必要運(yùn)用一些軟件來(lái)分析程序的性能瓶頸牡彻,Unix系統(tǒng)中有一個(gè)GPROF可以實(shí)現(xiàn)相關(guān)分析扫沼,在此不做講解了。

備注:(Amdahl定律)Gene Amdahl曾經(jīng)發(fā)現(xiàn)過(guò)一個(gè)很有意思的現(xiàn)象庄吼,最后以Amdahl命名了這個(gè)定律缎除,大意就是:當(dāng)我們選擇提高某個(gè)系統(tǒng)的效率的時(shí)候,被改進(jìn)的這一部分效率总寻,對(duì)整體的影響器罐,在于被改進(jìn)部分到底有多重要(聽起來(lái)像廢話)。我們來(lái)舉個(gè)例子渐行,加入一個(gè)軟件的耗時(shí)分為Told=(1+6+3)=10我們將6這個(gè)部分的效率提高了3倍轰坊,變成了Tnew=(1+2+3)=6。整個(gè)系統(tǒng)的加速還是不大祟印。這就是Amdahl定律要告訴我們的主要觀點(diǎn)肴沫,要想獲得整體性能的提升,我們必須要提高很大一部分系統(tǒng)的速度蕴忆。單靠一個(gè)方面是不行的樊零。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子驻襟,更是在濱河造成了極大的恐慌夺艰,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沉衣,死亡現(xiàn)場(chǎng)離奇詭異郁副,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)豌习,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門存谎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肥隆,你說(shuō)我怎么就攤上這事既荚。” “怎么了栋艳?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵恰聘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我吸占,道長(zhǎng)晴叨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任矾屯,我火速辦了婚禮兼蕊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘件蚕。我一直安慰自己孙技,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布排作。 她就那樣靜靜地躺著牵啦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纽绍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天势似,我揣著相機(jī)與錄音拌夏,去河邊找鬼。 笑死履因,一個(gè)胖子當(dāng)著我的面吹牛障簿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播栅迄,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼站故,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起西篓,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤愈腾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后岂津,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虱黄,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年吮成,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了橱乱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粱甫,死狀恐怖泳叠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茶宵,我是刑警寧澤危纫,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站节预,受9級(jí)特大地震影響叶摄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜安拟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一蛤吓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧糠赦,春花似錦会傲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至顾瞻,卻和暖如春泼疑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荷荤。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工退渗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕴纳。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓会油,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親古毛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子翻翩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容