這篇文章是極客上閱讀量很大的一篇文章,經過博友的翻譯,加上個人轉載和小改動,以便常常查看,方便之後進行代碼優化時拿來作參考。算法
· funccost是函數func運行時間百分比,funcspeedup是你優化函數的運行的係數。編程
· 因此,若是你優化了函數TriangleIntersect執行40%的運行時間,使它運行快了近兩倍,而你的程序會運行快25%。數組
· 這意味着不常用的代碼不須要作較多優化考慮(或者徹底不優化)。性能優化
· 這裏有句俗語:讓常常執行的路徑運行更加高效,而運行稀少的路徑正確運行。數據結構
· 這並不意味着用8周時間寫一個全功能的射線追蹤算法,而後用8周時間去優化它。多線程
· 分多步來作性能優化。函數
· 先寫正確的代碼,當你意識到這個函數可能會被常常調用,進行明顯的優化。性能
· 而後再尋找算法的瓶頸,並解決(經過優化或者改進算法)。一般,改進算法能顯著地改進瓶頸——也許是採用一個你尚未預想到的方法。全部頻繁調用的函數,都須要優化。優化
· 函數調用須要兩次跳轉,外加棧內存操做。spa
· 優先使用迭代而不是遞歸。
· 使用內聯函數處理短小的函數來消除函數調用開銷。
· 將循環內的函數調用移動到循環外(例如,將for(i=0;i<100;i++) DoSomething();改成DoSomething() { for(i=0;i<100;i++) { … }})。
· if…elseif…else if…else if…很長的分支鏈執行到最後的分支須要不少的跳轉。若是可能,將其轉換爲一個switch聲明語句,編譯器有時候會將其轉換爲一個表查詢單次跳轉。若是switch聲明不可行,將最多見的場景放在if分支鏈的最前面。
· 兩階或更高階的數組在內存中仍是以一維的方式在存儲在內存中,這意味着(對於C/C++數組)array[i][j] 和 array[i][j+1]是相鄰的,可是array[i][j] 和array[i+1][j]可能相距很遠。
· 以適當的方式訪問存儲實際內存中的數據,能夠顯著地提高你代碼的執行效率(有時候能夠提高一個數量級甚至更多)。
· 現代處理器從主內存中加載數據處處理器cache,會加載比單個值更多的數據。該操做會獲取請求數據和相鄰數據(一個cache行大小)的整塊數據。這意味着,一旦array[i][j]已經在處理器cache中,array[i][j+1]很大可能也已經在cache中了,而array[i+1][j]可能還在內存中。
· 儘管許多程序仍是依賴單線程的執行,現代處理器在單核中也提供了很多的並行性。例如:單個CPU能夠同時執行4個浮點數乘,等待4個內存請求並執行一個分支預判。
· 爲了最大化利用這種並行性,代碼塊(在跳轉之間的)須要足夠的獨立指令來容許處理器被充分利用。
· 考慮展開循環來改進這一點。
· 這也是使用內聯函數的一個好理由。
· 本地變量一般都存儲在棧上。不過若是數量比較少,它們能夠存儲在CPU寄存器中。在這種狀況下,函數不但獲得了更快訪問存儲在寄存器中的數據的好處,也避免了初始化一個棧幀的開銷。
· 不要將大量數據轉換爲全局變量。
· 和減小使用本地變量的理由同樣——它們也是存放在棧上。
· 我在射線追蹤中還找不到一個場景須要將結構體使用傳值方式(包括一些簡單結構如:Vector,Point和Color)。
· 整數和浮點數指令一般操做不一樣的寄存器,因此轉換須要進行一次拷貝操做。
· 短整型(char和short)仍然使用一整個寄存器,而且它們須要被填充爲32/64位,而後在存儲回內存時須要再次轉換爲小字節(不過,這個開銷必定比一個更大的數據類型的內存開銷要多一點)。
· 使用類初始化而不是使用賦值(Color c(black); 比Color c; c = black;更快)
· 尤爲是經常使用的簡單類型(好比,color,vector,point等等),這些類常常被複制。
· 這些默認構造函數一般都是在隱式執行的,這或許不是你所指望的。
· 使用類初始化列表(Use Color::Color() : r(0), g(0),b(0) {},而不是初始化函數Color::Color() { r= g =b = 0; } .)
· 在其餘狀況下,查找表會頗有用。對於GPU編程一般優先使用表查找而不是複雜函數。
· 這些簡單操做須要建立一個匿名臨時中間變量。
· 例如:Vector v = Vector(1,0,0) + Vector(0,1,0) +Vector(0,0,1);?建立了五個匿名臨時Vector: Vector(1,0,0),Vector(0,1,0), Vector(0,0,1), Vector(1,0,0) + Vector(0,1,0), 和 Vector(1,0,0) + Vector(0,1,0) + Vector(0,0,1).
· 對上述代碼進行簡單轉換:Vector v(1,0,0); v+= Vector(0,1,0); v+=Vector(0,0,1);僅僅建立了兩個臨時Vector: Vector(0,1,0) 和 Vector(0,0,1)。這節約了6次函數調用(3次構造函數和3次析構函數)。
· 定義一個對象變量一般須要調用一次函數(構造函數)。
· 若是一個變量只在某些狀況下須要(例如在一個if聲明語句內),僅在其須要的時候定義,這樣,構造函數僅在其被使用的時候調用。
· 使用後綴操做符須要執行一次對象拷貝(這也致使了額外的構造和析構函數調用),而前綴的構造函數不須要一個臨時的拷貝。
· 對不一樣的是實例實現進行不一樣的優化。
· 標準模板庫已經通過良好的優化,不過我建議你在實現一個交互式射線追蹤算法時避免使用它。
· 使用本身的實現,你知道它如何使用算法,因此你知道如何最有效的實現它。
· 最重要的是,個人經歷告訴我:調試STL庫很是低效。一般這也不是一個問題,除非你使用debug版本作性能分析。你會發現STL的構造函數,迭代器和其餘一些操做,佔用了你15%的運行時間,這會致使你分析性能輸出更加費勁。
· 動態內存對於存儲場景和運行期間其餘數據都頗有用。
· 可是,在許多(大多數)的系統動態內存分配須要獲取控制訪問分配器的鎖。對於多線程應用程序,現實中使用動態內存因爲額外的處理器致使了性能降低,由於須要等待分配器鎖和釋放內存。
· 即使對於單線程應用,在堆上分配內存也比在棧上分配內存開銷大得多。操做系統還須要執行一些操做來計算並找到適合尺寸的內存塊。
· 若是一個是數據結構正好適合一個cache行,處理整個類從內存中只須要作一次獲取操做。
· 確保全部的數據結構都是cache行大小對齊(若是你的數據結構和一個cache行大小都是128字節,仍有可能由於你的結構體中的一個字節在一個cache行中,而其餘127字節在另一個cahce行中)。
· 若是你須要初始化一大段的內存,考慮使用memset。
· 許多方程式中,一般均可以或者在某些條件中取消計算。
· 編譯器不能發現這些簡化,可是你能夠。取消一個內部循環的一些昂貴操做能夠抵消你在其餘地方的好幾天的優化工做。
· 在現代CPU,浮點數運算和整數運算差很少擁有一樣的效率。在計算密集型應用(好比射線追蹤),這意味這能夠忽略整數和浮點數計算的開銷差別。這也就是說,你沒必要要對算數進行整數處理優化。
· 雙精度浮點數運算也不比單精度浮點數運算更慢,尤爲是在64位機器上。
· sqrt()常常能夠被優化掉,尤爲是在比較兩個值的平方根是否一致時。
· 若是你重複地須要處理 除x 操做,考慮計算1/x的值,乘以它。這在向量規範化(3次除法)運算中贏得了大的改進,不過我最近發現也有點難以肯定的。不過,這仍然有所改進,若是你要進行三次或更多除法運算。
· 若是你在執行一個循環,那些在循環中執行不發生變化的部分,確保提取到循環外部。
· 考慮看看你的計算值是否能夠在循環中修改獲得(而不每次都從新開始循環計算)。