Dr. Dobb’s Blogger的Walter Bright曾寫(xiě)了一篇博文《Overlooked Essentials For Optimizing Code》,為我們總結了兩個(gè)最容易被人忽略的基本代碼優(yōu)化技術(shù)。酷殼個(gè)人網(wǎng)站版主陳皓對本文進(jìn)行了翻譯,現轉載于此,供大家學(xué)習。全文如下:
我編寫(xiě)程序至今有35年了,我做了很多關(guān)于程序執行速度方面優(yōu)化的工(一個(gè)示例),我也看過(guò)其它人做的優(yōu)化。我發(fā)現有兩個(gè)最基本的優(yōu)化技術(shù)總是被人所忽略。
注意,這兩個(gè)技術(shù)并不是避免時(shí)機不成熟的優(yōu)化。并不是把冒泡排序變成快速排序(算法優(yōu)化)。也不是語(yǔ)言或是編譯器的優(yōu)化。也不是把 i*4寫(xiě)成i<<2 的優(yōu)化。
這兩個(gè)技術(shù)是:
使用 一個(gè)profiler。
查看程序執行時(shí)的匯編碼。
使用這兩個(gè)技術(shù)的人將會(huì )成功地寫(xiě)出運行快的代碼,不會(huì )使用這兩個(gè)技術(shù)的人則不行。下面讓我為你細細道來(lái)。
使用一個(gè) Profiler
我們知道,程序運行時(shí)的90%的時(shí)間是用在了10%的代碼上。我發(fā)現這并不準確。一次又一次地,我發(fā)現,幾乎所有的程序會(huì )在1%的代碼上花了99%的運行時(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,但是自從購買(mǎi)這個(gè)Profiler后,它的包裝3年來(lái)還是那么的暫新。為什么人們不用?我真的不知道。有一次,我和我的同事去了一個(gè)負載過(guò)大的交易所,我同事堅持說(shuō)他知道哪里是瓶頸,畢竟,他是一個(gè)很有經(jīng)驗的專(zhuān)家。最終,我把我的Profiler在他的項目上運行了一下,我們發(fā)現那個(gè)瓶頸完全在一個(gè)意想不到的地方。
就像是賽車(chē)一樣。團隊是贏(yíng)在傳感器和日志上,這些東西提供了所有的一切。你可以調整一下賽車(chē)手的褲子以讓其在比賽過(guò)程中更舒服,但是這不會(huì )讓你贏(yíng)得比賽,也不會(huì )讓你更有競爭力。如果你不知道你的速度上不去是因為引擎、排氣裝置、空體動(dòng)力學(xué)、輪胎氣壓,或是賽車(chē)手,那么你將無(wú)法獲勝。編程為什么會(huì )不同呢?只要沒(méi)有測量,你就永遠無(wú)法進(jìn)步。
這個(gè)世界上有太多可以使用的Profiler了。隨便找一個(gè)你就可以看到你的函數的調用層次,調用的次數,以前每條代碼的時(shí)間分解表(甚至可以到匯編級)。我看過(guò)太多的程序員回避使用Profiler,而是把時(shí)間花在那些無(wú)用的,錯誤的方向上的“優(yōu)化”,而被其競爭對手所羞辱。(譯者陳皓注:使用Profiler時(shí),重點(diǎn)需要關(guān)注:1)花時(shí)間多的函數以?xún)?yōu)化其算法,2)調用次數巨多的函數——如果一個(gè)函數每秒被調用300K次,你只需要優(yōu)化出0.001毫秒,那也是相當大的優(yōu)化。這就是作者所謂的1%的代碼占用了99%的CPU時(shí)間)
查看匯編代碼
幾年前,我有一個(gè)同事,Mary Bailey,她在華盛頓大學(xué)教矯正代數(remedial algebra),有一次,她在黑板上寫(xiě)下:
x + 3 = 5
然后問(wèn)他的學(xué)生“求解x”,然后學(xué)生們不知道答案。于是她寫(xiě)下:
__ + 3 = 5
然后,再問(wèn)學(xué)生“填空”,所有的學(xué)生都可以回答了。未知數x就像是一個(gè)有魔法的字母讓大家都在想“x意味著(zhù)代數,而我沒(méi)有學(xué)過(guò)代數,所以我就不知道這個(gè)怎么做”。
匯編程序就是編程世界的代數。如果某人問(wèn)我“inline函數是否被編譯器展開(kāi)了?”或是問(wèn)我“如果我寫(xiě)下i*4,編譯器會(huì )把其優(yōu)化為左移位操作嗎?”。這個(gè)時(shí)候,我都會(huì )建議他們看看編譯器的匯編碼。這樣的回答是不是很粗暴和無(wú)用?通常,在我這樣回答了提問(wèn)者后,提問(wèn)都通常都會(huì )說(shuō),對不起,我不知道什么是匯編!甚至C++的專(zhuān)家都會(huì )這么回答。
匯編語(yǔ)言是最簡(jiǎn)單的編程語(yǔ)言了(就算是和C++相比也是這樣的),如:
ADD ESI,x
就是(C風(fēng)格的代碼)
ESI += x;
而:
CALL foo
則是:
foo();
細節因為CPU的種類(lèi)而不同,但這就是其如何工作的。有時(shí)候,我們甚至都不需要細節,只需要看看匯編碼的長(cháng)啥樣,然后和源代碼比一比,你就可以知道匯編代碼很多很多了。
那么,這又如何幫助代碼優(yōu)化?舉個(gè)例子,我幾年前認識一個(gè)程序員認為他應該去發(fā)現一個(gè)新的更快的算法。他有一個(gè)benchmark來(lái)證明這個(gè)算法,并且其寫(xiě)了一篇非常漂亮的文章關(guān)于他的這個(gè)算法。但是,有人看了一下其原來(lái)算法以及新算法的匯編,發(fā)現了他的改進(jìn)版本的算法允許其編譯器把兩個(gè)除法操作變成了一個(gè)。這和算法真的沒(méi)有什么關(guān)系。我們知道除法操作是一個(gè)很昂貴的操作,并且在其算法中,這倆個(gè)除法操作還在一個(gè)內嵌循環(huán)中,所以,他的改進(jìn)版的算法當然要快一些。但,只需要在原來(lái)的算法上做一點(diǎn)點(diǎn)小的改動(dòng)——使用一個(gè)除法操作,那么其原來(lái)的算法將會(huì )和新的一樣快。而他的新發(fā)現什么也不是。
下一個(gè)例子,一個(gè)D用戶(hù)張貼了一個(gè) benchmark 來(lái)顯示 dmd (Digital Mars D編譯器)在整型算法上的很糟糕,而ldc (LLVM D 編譯器)就好很多了。對于這樣的結果,其相當的有意見(jiàn)。我迅速地看了一下匯編,發(fā)現兩個(gè)編譯器編譯出來(lái)相當的一致,并沒(méi)有什么明顯的東西要對2:1這么大的不同而負責。但是我們看到有一個(gè)對long型整數的除法,這個(gè)除法調用了運行庫。而這個(gè)庫成為消耗時(shí)間的殺手,其它所有的加減法都沒(méi)有速度上的影響。出乎意料地,benchmark和算法代碼生成一點(diǎn)關(guān)系也沒(méi)有,完全就是long型整數的除法的問(wèn)題。這暴露了在dmd的運行庫中的long型除法的實(shí)現很差。修正后就可以提高速度。所以,這和編譯器沒(méi)有什么關(guān)系,但是如果不看匯編,你將無(wú)法發(fā)現這一切。
查看匯編代碼經(jīng)常會(huì )給你一些意想不到的東西讓你知道為什么程序的性能是那樣。一些意想不到的函數調用,預料不到的自傲,以及不應該存在的東西,等等其實(shí)所有的一切。但也不需要成為一個(gè)匯編代碼的黑客才能干的事。
結論
如果你覺(jué)得需要程序有更好的執行速度,那么,最基本的方法就是使用一個(gè)profiler和愿意去查看一下其匯編代碼以找到程序的瓶頸。只有找到了程序的瓶頸,此時(shí)才是真正在思考如何去改進(jìn)的時(shí)候,比如思考一個(gè)更好的算法,使用更快的語(yǔ)言?xún)?yōu)化,等等。
常規的做法是制勝法寶是挑選一個(gè)最佳的算法而不是進(jìn)行微優(yōu)化。雖然這種做法是無(wú)可異議的,但是有兩件事情是學(xué)校沒(méi)有教給你而需要你重點(diǎn)注意的。第一個(gè)也是最重要的,如果你優(yōu)化的算法沒(méi)沒(méi)有參與到你程序性能中的算法,那么你優(yōu)化他只是在浪費時(shí)間和精力,并且還轉移了你的注意力讓你錯過(guò)了應該要去優(yōu)化的部分。第二點(diǎn),算法的性能總和處理的數據密切相關(guān)的,就算是冒泡排序有那么多的笑柄,但是如果其處理的數據基本是排好序的,只有其中幾個(gè)數據是未排序的,那么冒泡排序也是所有排序算法里性能最好的。所以,擔心沒(méi)有使用好的算法而不去測量,只會(huì )浪費時(shí)間,無(wú)論是你的還是計算機的。
就好像賽車(chē)零件的訂購速底是不會(huì )讓你更靠進(jìn)冠軍(就算是你正確安裝零件也不會(huì )),沒(méi)有Profiler,你不會(huì )知道問(wèn)題在哪里,不去看匯編,你可能知道問(wèn)題所在,但你往往不知道為什么。
聯(lián)系客服