代碼優化概要

本文譯自Dr. Dobb’s Blogger的Walter Bright寫的《Overlooked Essentials For Optimizing Codehtml

git


我編寫程序至今有35年了,我作了不少關於程序執行速度方面優化的工(一個示例), 我也看過其它人作的優化。我發現有兩個最基本的優化技術老是被人所忽略。   注意,這兩個技術並非避免時機不成熟的優化。並非把冒泡排序變成快速排序(算法優化)。也不是語言或是編譯器的優化。也不是把 i*4寫成i<<2 的優化。  這兩個技術是:程序員

  1. 使用 一個profiler。算法

  2. 查看程序執行時的彙編碼。編程

使用這兩個技術的人將會成功地寫出運行快的代碼,不會使用這兩個技術的人則不行。下面讓我爲你細細道來。編程語言

使用一個 Profiler

咱們知道,程序運行時的90%的時間是用在了10%的代碼上。我發現這並不許確。一次又一次地,我發現,幾乎全部的程序會在1%的代碼上花了99% 的運行時間。可是,是哪一個1%?一個好的Profiler能夠告訴你這個答案。就算咱們須要使用100個小時在這1%的代碼上進行優化,也比使用100個 小時在其它99%的代碼上優化產生的效益要高得多得多。 問題是什麼?人們不用profiler?不是。我工做過的一個地方使用了一個華麗而奢侈的Profiler,可是自從購買這個Profiler後,它的包 裝3年來仍是那麼的暫新。爲何人們不用?我真的不知道。有一次,我和個人同事去了一個負載過大的交易所,我同事堅持說他知道哪裏是瓶頸,畢竟,他是一個 頗有經驗的專家。最終,我把個人Profiler在他的項目上運行了一下,咱們發現那個瓶頸徹底在一個意想不到的地方。   就像是賽車同樣。團隊是贏在傳感器和日誌上,這些東西提供了全部的一切。你能夠調整一下賽車手的褲子以讓其在比勝過程中更舒服,可是這不會讓你贏得比賽, 也不會讓你更有競爭力。若是你不知道你的速度上不去是由於引擎、排氣裝置、空體動力學、輪胎氣壓,或是賽車手,那麼你將沒法獲勝。編程爲何會不一樣呢?只 要沒有測量,你就永遠沒法進步。   這個世界上有太多可使用的Profiler了。隨便找一個你就能夠看到你的函數的調用層次,調用的次數,之前每條代碼的時間分解表(甚至能夠到彙編 級)。我看過太多的程序員迴避使用Profiler,而是把時間花在那些無用的,錯誤的方向上的「優化」,而被其競爭對手所羞辱。(譯者陳皓注:使用Profiler時,重點須要關注:1)花時間多的函數以優化其算法,2)調用次數巨多的函數——若是一個函數每秒被調用300K次,你只須要優化出0.001毫秒,那也是至關大的優化。這就是做者所謂的1%的代碼佔用了99%的CPU時間)函數

查看彙編代碼

幾年前,我有一個同事,Mary Bailey,她在華盛頓大學教矯正代數(remedial algebra),有一次,她在黑板上寫下:  x + 3 = 5 而後問他的學生「求解x」,而後學生們不知道答案。因而她寫下:  __ + 3 = 5 而後,再問學生「填空」,全部的學生均可以回答了。未知數x就像是一個有魔法的字母讓你們都在想「x意味着代數,而我沒有學過代數,因此我就不知道這個怎 麼作」。   彙編程序就是編程世界的代數。若是某人問我「inline函數是否被編譯器展開了?」或是問我「若是我寫下i*4,編譯器會把其優化爲左移位操做嗎?」。 這個時候,我都會建議他們看看編譯器的彙編碼。這樣的回答是否是很粗暴和無用?一般,在我這樣回答了提問者後,提問都一般都會說,對不起,我不知道什麼是 彙編!甚至C++的專家都會這麼回答。  彙編語言是最簡單的編程語言了(就算是和C++相比也是這樣的),如:性能

ADD ESI,x優化

就是(C風格的代碼)編碼

ESI += x;

而:

CALL foo

則是:

foo();

細節由於CPU的種類而不一樣,但這就是其如何工做的。有時候,咱們甚至都不須要細節,只須要看看彙編碼的長啥樣,而後和源代碼比一比,你就能夠知道 彙編代碼不少不少了。   那麼,這又如何幫助代碼優化?舉個例子,我幾年前認識一個程序員認爲他應該去發現一個新的更快的算法。他有一個benchmark來證實這個算法,而且其 寫了一篇很是漂亮的文章關於他的這個算法。可是,有人看了一下其原來算法以及新算法的彙編,發現了他的改進版本的算法容許其編譯器把兩個除法操做變成了一 個。這和算法真的沒有什麼關係。咱們知道除法操做是一個很昂貴的操做,而且在其算法中,這倆個除法操做還在一個內嵌循環中,因此,他的改進版的算法固然要 快一些。但,只須要在原來的算法上作一點點小的改動——使用一個除法操做,那麼其原來的算法將會和新的同樣快。而他的新發現什麼也不是。   下一個例子,一個D用戶張貼了一個 benchmark 來顯示 dmd (Digital Mars D 編譯器)在整型算法上的很糟糕,而ldc (LLVM D 編譯器) 就好不少了。對於這樣的結果,其至關的有意見。我迅速地看了一下彙編,發現兩個編譯器編譯出來至關的一致,並無什麼明顯的東西要對2:1這麼大的不一樣而 負責。可是咱們看到有一個對long型整數的除法,這個除法調用了運行庫。而這個庫成爲消耗時間的殺手,其它全部的加減法都沒有速度上的影響。出乎意料 地,benchmark 和算法代碼生成一點關係也沒有,徹底就是long型整數的除法的問題。這暴露了在dmd的運行庫中的long型除法的實現不好。修正後就能夠提升速度。所 以,這和編譯器沒有什麼關係,可是若是不看彙編,你將沒法發現這一切。   查看彙編代碼常常會給你一些意想不到的東西讓你知道爲何程序的性能是那樣。一些意想不到的函數調用,預料不到的自傲,以及不該該存在的東西,等等其實所 有的一切。但也不須要成爲一個彙編代碼的黑客才能乾的事。

結論

若是你以爲須要程序有更好的執行速度,那麼,最基本的方法就是使用一個profiler和願意去查看一下其彙編代碼以找到程序的瓶頸。只有找到了程 序的瓶頸,此時纔是真正在思考如何去改進的時候,好比思考一個更好的算法,使用更快的語言優化,等等。   常規的作法是制勝法寶是挑選一個最佳的算法而不是進行微優化。雖然這種作法是無可異議的,可是有兩件事情是學校沒有教給你而須要你重點注意的。第一個也是 最重要的,若是你優化的算法沒沒有參與到你程序性能中的算法,那麼你優化他只是在浪費時間和精力,而且還轉移了你的注意力讓你錯過了應該要去優化的部分。 第二點,算法的性能總和處理的數據密切相關的,就算是冒泡排序有那麼多的笑柄,可是若是其處理的數據基本是排好序的,只有其中幾個數據是未排序的,那麼冒 泡排序也是全部排序算法裏性能最好的。因此,擔憂沒有使用好的算法而不去測量,只會浪費時間,不管是你的仍是計算機的。   就好像賽車零件的訂購速底是不會讓你更靠進冠軍(就算是你正確安裝零件也不會),沒有Profiler,你不會知道問題在哪裏,不去看彙編,你可能知道問 題所在,但你每每不知道爲何。  (全文完)

相關文章
相關標籤/搜索