說明:html
無心看到一篇小短文,猜想做者應該是一個圖形學領域的程序員或專家,介紹了在光線(射線)追蹤程序中是如何優化C/C++代碼的。倒也有一些參考意義,固然有的地方我並不贊同或者說我也不徹底理解,原文在此,個人粗糙翻譯以下:程序員
1. 牢記Ahmdal定律算法

- funccost表示是函數func的運行時間百分比,funcspeedup是你優化後函數的運行係數;
- 因此,若是函數TriangleIntersect()佔用40%的運行時間,而在你優化後使它運行快了兩倍,那麼你的程序運行可以快了25%;
- 這意味着不常用的代碼不須要作過多優化(或者徹底不優化),好比場景加載過程;
- 也就是:讓頻繁調用的代碼運行得更加高效,而讓較少調用的代碼保持運行正確;
2. 先有正確的代碼,而後再作優化編程
- 這並非說先花8個周時間寫一個全功能的光線追蹤器,而後再花8個周去優化;
- 而是在你的管線追蹤程序中的多個階段都進行優化;
- 若是代碼是正確的,而你又知道哪些函數會被頻繁的調用,優化是很明顯的;
- 而後找到瓶頸所在,並去除瓶頸(經過優化或者算法改進)。一般來講改進算法能夠很顯著地優化瓶頸——甚至可能採用了一個你沒想到的算法。優化那些你所知道的將被頻繁調用的函數是一個很好的作法;
3. 那些我認識的可以寫出很是高效的代碼的人說,他們花費在優化代碼上的時間是他們寫代碼時間的至少兩倍以上 數組
4. 跳轉/分支語句是昂貴的,無論什麼時候儘量的減小使用緩存
- 函數調用除了棧存儲操做外,還須要兩次跳轉;
- 優先選擇迭代,而不是遞歸;
- 若是是短函數,使用內聯來消除函數開銷;
- 將循環放在函數內(例如將for(i=0;i<100;i++) DoSomething();改成在DoSomething()內作DoSomething());
- 長長的if...else if...else if...else if...語句鏈須要大量的跳轉才能結束(除了在測試每一個條件時)。若是可能,改成switch語句,有時編譯器能夠有優化爲在一個表中查找和單級跳轉。若是switch語句是不可能的,那把最常常走到的if語句放在語句鏈開頭;
5. 考慮數組索引的順序數據結構
- 兩維或更多維的數組在內存中還是按一維存儲的。這意思是array[i][j]和 array[i][j+1]是相鄰的(C/C++代碼),然而array[i][j]和array[i+1][j]卻能夠相離的任意遠;
- 訪問物理內存中的連續數據,能夠顯著加快你的代碼(有時是一個數量級,甚至更多);
- 如今CPU從主內存中加載數據到高速緩存時,它不只僅是隻加載單一數據,而是加載一塊數據,既包含了要請求的數據,也包含部分相鄰數據(一個cache行)。這意思是說若是array[i][j]在CPU緩存中,那麼array[i][j+1]就頗有可能也在緩存中了,然而array[i+1][j]可能仍在內存中;
6. 考慮指令級並行性(IPL)多線程
- 儘管不少程序還是單線程執行,但現代的CPU已經可以在單核上有顯著的並行性。這意味着單CPU也可能同時執行4個浮點數乘法、等待4個內存請求,並執行即將到來的分支比較操做
- 爲了充分利用這種並行性,代碼塊(好比在跳轉語句中)須要足夠的獨立指令來使CPU獲得充分使用;
- 能夠考慮經過展開循環來改進;
- 這也是使用內聯函數的一個很好的緣由;
7. 避免或減小局部變量的使用ide
- 局部變量一般是存儲在棧上。若是不多,能夠存儲在寄存器中。在這種狀況下,函數不只獲得了對存儲在寄存器上的數據的更快內存訪問的好處,也能夠避免創建一個棧幀的開銷;
- 可是,也不要把全部對象都全盤聲明爲全局變量;
8. 減小函數參數的個數函數
- 和減小局部變量的緣由同樣——他們也是在棧上存儲的;
9. 結構體(包括類)傳參時使用傳引用而不是傳值
- 在光線追蹤程序中,哪怕是簡單如vector、points、colors等結構,我也沒有見過使用值傳遞的代碼
10. 若是你不須要一個函數的返回值,那就不要返回
11. 儘量避免使用轉型操做
- 整數和浮點數的指令集一般在不一樣的寄存器上運算,所以轉型操做須要拷貝操做;
- 短整形(char和short)仍然須要一個全尺寸的寄存器,並且在存儲回內存以前,它們須要對齊到32位或64位上,而後才轉換成更小尺寸類型;
12. 當定義C++對象時必定要當心
- 使用初始化(Color c(black))而不是賦值(Color c, c = black),而前者更快;
13. 使類的默認構造函數儘量的輕量
- 特別是那簡單的、常用的類(例如,顏色,矢量,點等);
- 這些默認構造函數一般是在你不注意時就調用,甚至那時你並不但願這樣;
- 使用構造初始化列表(使用Color::Color() : r(0), g(0), b(0) {}而不是Color::Color() { r = g = b = 0; } );
14. 儘量使用移位操做符>>和<<,而不是整數乘法和除法
15. 當心使用查表功能
- 不少人鼓勵對於複雜的功能(例如,三角函數)使用預先計算過值的查表法。對於光線跟蹤程序來講,這每每是沒必要要的。內存查找是很是(日益)昂貴的,並且從新計算三角函數每每和從內存中查找值同樣快(尤爲是當你考慮到內存查找會影響CPU緩存命中率時);
- 在其它狀況下,查表多是很是有用的。好比在GPU編程中,查表法一般是複雜功能的優先選擇;
16. 對於大多數的類類型,使用運算符 +=,-=,*=和/=,而少用+,-,*,/
- 這類簡單操做其實須要建立一個匿名名的、臨時的中間對象;
- 例如Vector v = Vector(1,0,0) + Vector(0,1,0) + Vector(0,0,1) 語句建立了5個未命名、臨時的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); 這樣僅僅建立了2個臨時Vector:Vector v(1,0,0) 和Vector(0,0,1),而節省了6個函數調用(3個構造和3個析構);
17. 對於基本數據類型,使用運算符+,-,*,/,而少用+=,-=,*=和/=
18. 延遲局部變量的定義時間
- 定義一個對象總會有一個函數調用開銷(就是構造函數)
- 若是一個對象只是有時候才被使用(好比在一個if語句內部),那麼就只在必要時才定義,由於這樣就只當這個變量使用時纔會調用它的構造函數
19. 對於對象來講,使用前綴操做符(++obj),而不是後綴操做符(obj++)
- 在你的光線追蹤程序中,這可能並非個問題
- 對象的拷貝操做必須使用後綴操做符(這須要額外調用一個構造和一個析構函數),而前綴操做符並不產生臨時對象
20. 慎用模板
- 各類具現化實例的優化方式多是不一樣的;
- 標準模板庫(STL)作了很好的優化,但若是你打算實現交互式光線跟蹤器,最好是仍避免使用;
- 經過本身實現,你能清楚地明白要它使用的算法,你就會知道最有效的使用方式;
- 更重要的是,個人經驗代表調試、編譯STL會很慢。一般這也是沒問題的,除非你使用Debug版本進行性能分析。你會發現STL的構造、迭代器等操做會佔用運行時間的15%以上,它會使輸出的分析結果更爲混亂
21. 在計算過程當中避免動態內存分配
- 動態內存主要優點在於存儲場景數據和其餘數據,而不是在計算過程當中進行修改
- 然而,在許多(大多數)時候系統動態存儲分配要求使用鎖來控制訪問分配器。對於使用動態內存的多線程應用程序來講,因爲須要等待分配和釋放內存,經過這些額外的處理,你可能實際上獲得的是一個更慢的程序
- 即便在單線程程序中,在堆上分配內存也比在棧上分配更昂貴。操做系統須要進行一些計算來肯定所需大小的內存塊。
22. 發現和充分利用有關你的系統內存Cache的有用信息
- ü 若是一個數據結構大小剛好填滿一個Cache行,處理整個類只須要從內存中讀取一次;
- ü 確保全部的數據結構都能對齊到Cache邊界(若是你的數據大小和Cache都是128字節,那麼當1個字節在一個Cache行而另外127字節在第二個Cache行時,那麼性能仍然很差)
23. 避免沒必要要的數據初始化
- 若是你要初始化一大塊內存,考慮用memset()函數
24. 儘可能提前結束循環判斷和函數返回
- 考慮射線和三角形相交。常見狀況是射線和三角形不相交,所以這裏能夠優化;
- 若是你要判斷射線和三角形相交的狀況,一旦t值射線平面爲負,你能夠當即返回。這樣可使你跳過大約一半的光線三角形交叉點的重心座標計算。一個巨大的勝利!一旦你肯定沒有相交發生,求交函數就應該退出
- 一樣的,一些循環也能夠被提前結束。例如,在光線陰影設置中,最近的相交是沒必要要的。只要發現了任何交叉閉環,求交函數就能夠返回
25. 先在紙上簡化你使用的公式
- 在不少公式中,老是能夠或者一些特殊狀況下,能夠取消計算
- 編譯器找不到這些簡化,可是你能夠。消除一些內在循環中的昂貴操做能夠比你在其餘地方的優化更能加速你的程序
26. 對於整數型、定點數、32位浮點數、64位浮點數來講,他們之間的差異並無你想象中的那麼大
- 現代CPU進行浮點運算和整數運算其實有相同的運算吞吐量,像光線追蹤這種計算密集型的程序,這意思是整數和浮點運算成本之間的差別能夠忽略不計,這意味着你不須要作一些優化來使用整數運算;
- 雙精度浮點運算並不必定比單精度浮點計算更慢,尤爲是在64位機器上。我曾經在同一臺機器上測試光線追蹤算法,結果是有時所有使用double比所有使用float會運行得更快,
27. 考慮經過重寫你的數學公式來消除昂貴的操做