|
終于談到這個(gè)話題了,首先聲明我不是匯編優(yōu)化的高手,甚至于我知道的所有關(guān)于匯編優(yōu)化的內(nèi)容,僅僅來自于學(xué)校的課程、書本及當(dāng)年做過的一些簡單練習(xí)。換句話說,我了解的東西只能算是一些原則,甚至也有一些“陳舊”了——不過我想既然是一些原則性的東西,還是能夠用它來做一定程度的判斷。至少我認(rèn)為,我在博客園里看到的許多關(guān)于“匯編優(yōu)化”也好,“內(nèi)嵌匯編”也罷的說法,經(jīng)常是有些問題的。
說到匯編優(yōu)化,自然被人想到“高性能”。似乎用.NET或Java平臺(tái)上的程序性能一定不佳,性能好的程序一定要用C++——不,至少一定要用C來寫。為什么呢?因?yàn)橐粋€(gè)“常識(shí)”:便是“封裝”會(huì)損失性能。性能最高的是“機(jī)器碼”,因?yàn)镃PU直接執(zhí)行機(jī)器嗎;“匯編”作為機(jī)器碼的直接對(duì)應(yīng)產(chǎn)物性能自然是一致的;C語言對(duì)于匯編/機(jī)器碼幾乎沒有任何封裝,因此性能也很好;而到了C++語言時(shí),性能就要比C慢一些了——不過,這個(gè)看法正確嗎?
其實(shí)我最近這幾篇文章談的都是與程序性能,尤其是代碼執(zhí)行效率有關(guān)的話題。在上一篇文章里,我們可以知道即便是在使用匯編編寫代碼,同樣繞不開“CPU緩存”這部分與計(jì)算機(jī)體系結(jié)構(gòu)相關(guān)的內(nèi)容,而它對(duì)程序性能的影響甚至遠(yuǎn)遠(yuǎn)超過幾句指令本身。事實(shí)上這也只是一小個(gè)方面而已,我們平時(shí)在談性能相關(guān)問題時(shí),總是在做很多假設(shè),例如我們會(huì)假設(shè)不同指令的執(zhí)行速度是一樣的,各級(jí)別存儲(chǔ)的讀取性能也是相同的,但這都只是一個(gè)“理想環(huán)境”,和“事實(shí)”有很大差距。而進(jìn)行匯編級(jí)別的優(yōu)化,往往也是在利用“事實(shí)”進(jìn)行細(xì)枝末節(jié)的調(diào)整。
例如,假設(shè)編譯器只是對(duì)代碼做“直接翻譯”的話,您認(rèn)為以下兩種做法性能哪個(gè)比較好?
int sum = 0;for (int i = 0; i < 100; i++){ sum += array[i];}
int sum1 = 0, sum2 = 0;for (int i = 0; i < 100; i += 2){ sum1 += array[i]; sum2 += array[i + 1];}int sum = sum1 + sum2;
從算法上看,兩者完全相同,但是對(duì)于CPU來說,后一種做法比前一種做法性能要高。首先,第二段代碼與前者相比,一個(gè)循環(huán)內(nèi)部有兩個(gè)完全不相關(guān)的加法運(yùn)算,這樣CPU便有機(jī)會(huì)將他們并行地執(zhí)行,于是性能便會(huì)更好一些。其次,第二種做法的條件跳轉(zhuǎn)次數(shù)少,一般來說性能就會(huì)更好一些。因?yàn)闂l件跳轉(zhuǎn)直到最后一刻才知道要跳向何方,因此CPU流水線就很難對(duì)代碼的走向進(jìn)行預(yù)測(cè)了。當(dāng)然,現(xiàn)在CPU設(shè)計(jì)已經(jīng)引入了分支預(yù)測(cè)技術(shù),如果預(yù)測(cè)成功,效率自然較高,但如果預(yù)測(cè)失敗,那么便會(huì)有比較嚴(yán)重的損失了。因此,有時(shí)候“我們”會(huì)盡可能想辦法去減少條件跳轉(zhuǎn)的次數(shù)。
例如,求一個(gè)有符號(hào)32位整數(shù)的絕對(duì)值,按照我們普通的邏輯,它應(yīng)該是這樣的:
if (eax < 0) eax = -eax
這顯然是一個(gè)條件跳轉(zhuǎn),但是它的匯編實(shí)現(xiàn)也完全可以是:
cdq // 擴(kuò)展eax的符號(hào)位到edx中,如果eax是正數(shù)則edx為0否則edx為0xffffffffxor eax, edx // 如果eax為負(fù)數(shù),就把所有的位取反,否則不變sub eax, edx // 如果最開始eax為負(fù)數(shù),則把這個(gè)數(shù)字取反加一
這樣,原本的條件跳轉(zhuǎn)消失了,但是我們使用順序的匯編指令得到了正確的結(jié)果。
這樣看來,內(nèi)嵌匯編對(duì)于性能多么關(guān)鍵啊。但是,我們真需要親自動(dòng)手實(shí)現(xiàn)這些嗎?無論是前面的“循環(huán)展開”還是后面的“取絕對(duì)值”都是機(jī)械的匯編級(jí)別的優(yōu)化,這些正是編譯器最(包括運(yùn)行時(shí)里的JIT)擅長的優(yōu)化手段了。如果我們想要代替編譯器去做這些事情,基本上唯一的結(jié)果只是“丑陋的代碼”而難以有性能的提高。
編譯器其實(shí)是提高代碼執(zhí)行效率的重要工具,例如之前在談這個(gè)話題的時(shí)候,有人談到OCaml的性能比C/C++要高,這便是因?yàn)樗木幾g器并不需要像C/C++編譯器那樣作出最壞的打算——例如C/C++很多時(shí)候無法檢測(cè)出兩個(gè)變量之間的關(guān)系,因此只能按部就班地執(zhí)行。同樣,我們?yōu)槭裁凑fC語言中strlen()不應(yīng)該放在循環(huán)內(nèi)部,因?yàn)樗鼤?huì)造成重復(fù)計(jì)算?因?yàn)镃語言編譯器不能假設(shè)在循環(huán)過程中strlen的返回值永遠(yuǎn)不變,因此它無法自動(dòng)將其提取到循環(huán)外部,只能一遍遍地執(zhí)行。
因此很多時(shí)候,我們?cè)谶@方面必須為編譯器做點(diǎn)什么。例如,一個(gè)關(guān)于處理器的“常識(shí)”便是,不管是整數(shù)還是浮點(diǎn)數(shù),除法操作都比乘法要慢上許多,因此我們需要盡可能消除一些除法,例如在進(jìn)行圖片縮放的時(shí)候,我們需要確定縮放的依據(jù)是“寬”還是“高”,因此我們可能就會(huì)寫這樣的代碼:
if (desiredWidth / originalWidth < desiredHeight / originalHeight)
事實(shí)上,如果您要在性能上作精細(xì)地追求,則這樣是更好的做法:
if (desiredWidth * originalHeight < desiredHeight * originalWidth)
可惜的是,編譯器可能無法為我們自動(dòng)作這樣的優(yōu)化:我們的這些變量都是32為“有符號(hào)”整數(shù),因此originalWidth可能會(huì)是負(fù)數(shù)。雖然我們知道圖片的尺寸一定大于零,但是我們卻沒有辦法把這些信息告訴編譯器,因此編譯器只能做最保守的計(jì)算了。
看到這里您可能會(huì)說,這些是在談匯編優(yōu)化嗎?好像還是一直再說高級(jí)代碼啊。沒錯(cuò),因?yàn)檎騽偛潘f那樣,我不其實(shí)并不了解多少匯編優(yōu)化的內(nèi)容,我也只能說一些“大道理”。如果您對(duì)這方面有些“興趣”的話,云風(fēng)的《游戲之旅——我的編程感悟》一書似乎值得您一看(其實(shí)這篇文章的許多說法,都和這本書有密切關(guān)系)。在這本書里,云風(fēng)總結(jié)他在多年游戲開發(fā)中總結(jié)到的經(jīng)驗(yàn),其中有相當(dāng)部分便是匯編優(yōu)化方面的內(nèi)容。其中也討論了許多其他方面的問題,如文章開始我提到的C++和C語言的性能高低,他認(rèn)為C++的性能其實(shí)與C語言相比有過之而無不及,如果您在C語言里實(shí)現(xiàn)C++的特性(如多態(tài))則幾乎無法作的如C++一樣好,而反過來,如果在C++中做C語言寫過程式的代碼,其性能往往會(huì)比C語言來的好。為什么?語言特性與編譯器的威力唄。
如今的處理器,它的的優(yōu)化手段已經(jīng)非常高級(jí),遠(yuǎn)不是在加快時(shí)鐘頻率上那么簡單。這給了程序員手動(dòng)進(jìn)行匯編優(yōu)化的動(dòng)力,因?yàn)榇藭r(shí)可能只要交換兩條指令的順序便可以有很明顯的性能提高,而編譯器的力量已經(jīng)不足以作更細(xì)致的優(yōu)化了。同時(shí),CPU設(shè)計(jì)上的進(jìn)步也在敦促程序員要不斷更新自己的知識(shí),因?yàn)榭赡茉谂fCPU上常用的優(yōu)化方式,到了新的CPU上就不是那么明顯了。例如《游戲之旅》就用了“不小”的篇幅“簡單”描述了從Pentium到Pentium IV上漸進(jìn)的優(yōu)化方式。
當(dāng)然,我并不贊同以性能為尊的程序編寫方式,事實(shí)上匯編優(yōu)化遠(yuǎn)比編寫高級(jí)代碼更可能遇到麻煩。云風(fēng)在書上也強(qiáng)調(diào),不要過于信任自己的匯編書寫能力,即便像他這樣有豐富經(jīng)驗(yàn)的高手也遇到過不少令人大跌眼鏡的事情。
it知識(shí)庫:淺談代碼的執(zhí)行效率(4):匯編優(yōu)化,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。