對一次多核程序設計比賽的總結

比賽是公司搞的,要求在20天時間內完成一程序,求三維空間內所有射線同所有三角平面的交點。射線和三角平面都通過文件方式輸入,結果以文件輸出,程序最後測試的平臺有8核,8G的內存。算法,語言,線程庫都不做限制,任意發揮,最後以結果正確,速度最快者獲勝。我大概搞了七八天(由於放了長假還要上班,實際沒有20天,不過這個大家都一樣,公平),從對圖形學零知識,學習向量、點乘、叉乘等等這些基礎知識開始,到運用空間劃分、光線行進算法,加上多線程,最後也能跑出相對滿意的結果,雖然最後由於程序局部設計上的一點缺陷,導致公測的時候未跑出正確結果,但依然感觸良多,故在博客中開個處女貼,一是作爲紀念,二是希望能跟更多的技術愛好者共同交流,共同進步。

 

一:先說一下自己的整個比賽過程:

   當剛拿到題目的時候,除了需要先學習基本點圖形學基礎外,還在網上找了射線跟三角面求交的算法(也就幾十行代碼),然後很簡單的認爲只要每條射線跟每個三角面求一下交,所有的交點就能獲得了,在算法上還能做啥優化不成?這種想當然使我很快實現了整個程序,包括讀文件,寫文件以及求交。拿比賽委員會給的最大的測試數據一測,跑了十幾秒(由於本機是雙核,於是用了兩個線程來求交),然後想再做些優化,發現小小的程序已無太多優化的餘地,遂心中起疑慮,這種算法是個選手都能想到,線程的效用也都能發揮到最大,那最後比成績豈不是在拼運氣,於是覺得應該沒那麼簡單,但對於此前從未涉足過圖形學的我來說,也實在不知該從何處下手優化。此狀態持續了有三天左右,一直在一些小地方做優化,提高成績也很有限。

  之後跟一個同事聊起此比賽,由於他研究生讀的就是圖形學,大概跟我提了一下空間劃分、八叉數這些概念,突然感覺這裏面有戲,於是開始在網上搜索這些概念,找相關的資料、論文,果然,發現了一片大天地,於是花了一兩天時間好好研究了一把這些知識,並結合自身的需求,設計了出了一套算法。再馬不停蹄的用代碼實現,經過漫長的調試後一跑,時間降到了兩三秒,我的天呀,這種成就感太讓人興奮了,不過興奮之後並沒有忘乎所以,開始分功能模塊單獨測試運行時間,哪一塊所佔時間大就先優化哪一塊,時間在一點點縮短,令人感嘆的地方也越來越多,最後優化到600多毫秒的時候,離最後提交時間就差一小時了,於是匆忙提交。哎,說到這裏,有個教訓需要提一下,那就是疏忽了測試數據,誤以爲最後公測的數據也應該跟委員會提供的最大數據量差不多,可實際根本就不是一個數量級的,導致了程序在初始化的時候發生錯誤,從而前功盡棄。雖然說比賽以參與爲主,但既然參加了比賽,自然就希望能打敗對手,所以最後失敗也略感不爽。當然,這也算是個不大不小的經驗教訓,對自己今後還是會有幫助的。

 

二:下面主要從算法,數據結構,語言,多線程三個方面來總結優化的過程:

(1)      算法、數據結構永遠是計算機的核心,語言只是表達它們的一種工具,而多線程多核只能算是一道誘人的小菜。

從我參與的整個過程看,算法在這裏起到了決定性的作用,提高的性能絕不是多幾個核能解決的,而數據結構設計的好壞,不僅對算法的性能提高有很大幫助,而且對多線程發揮效用也起到了決定性作用,因爲好的數據結構能減少線程訪問公共數據的衝突,從而讓線程跑的更加暢通無阻。至於語言,我用的是C++,當然,是面向對象還是面向過程對這個比賽影響不大,主要是在使用C++庫上面需要好好考察一番,尤其是STL,當整個運行時間降到1s以下時,你會發現無論它的map類還是hash類都是那麼的慢,能佔個幾百毫秒,這時如果你消耗點內存,開個全散列的讀寫緩存,幾百毫秒一下就沒了,當然,這裏是以空間換時間。還有碰到的一個情況是,如果你要初始化幾百萬個對象,而這個對象裏包含了一個vector,此vector的功能僅僅是插入一些數據,那你還是老老實實自己寫個列表吧,因爲它的初始化也是幾百毫秒,當然,這裏的情況比較特殊,一是有幾百萬個對象,二是程序性能優化是在毫秒級別的。你要是碰不到這種情況,用了也無所謂。這裏就引出第二個結論。

 

(2)     不要輕易對性能瓶頸做出結論,一切都要在精確測量之後再確定。

直觀判斷的結論往往是不準確的,很多時候也很難做直觀的判斷,所以儘可能的插入一些統計數據,包括時間,循環執行次數等等有利於你做判斷的數據。

 

(3)     性能瓶頸是在不斷轉化的,當不起眼的部分在你優化了其它部分後,它就是你下一步要攻克的對象了。

當我把求交的模塊優化好之後,發現從得到交點到寫出文件耗費了很長時間,一開始以爲是從緩存把數據寫到文件時慢了,但一測其實速度是很快的,於是就往上找,最後定位在sprintf_s這個函數上面(需要把double轉換成char),一統計時間,乖乖,又是個幾百毫秒的傢伙,於是考慮自己寫double轉換成char的函數,哈,很多人可能會直觀覺得這會比較複雜,自己寫出錯風險較大,我一開始也這麼覺得,可沒辦法,此處不優化,我夜不能寐啊,於是開始分析所有double可能的值,如何設計特殊情況,大概60行左右代碼就完成功能,而sprintf_s據說有一千多行代碼,故性能提升也很明顯。

 

(4)     擴展你的知識面,最好對它們都有一定的瞭解,這能讓你在最短時間內做出最合適的選擇。

由於我對圖形學知識的匱乏,使得在前面浪費了很多寶貴時間;另外不瞭解OpenMP, TBB等這些線程庫,不僅縮小了我的選擇範圍,也增加了自己學習的時間。在這樣的比賽中,誰能更好的利用時間,誰就贏得了一半比賽。

 

(5)     先從大處着手優化,然後再優化小處,否則順序顛倒,就算你在小處優化的再好,也只能是個甜頭。所謂大處小處,請通過第2條來確定。

在時間有限的前提下,用最寶貴的時間來做最有利的事,往往這件事難度也會比較大,但不要任性去做自己喜歡或覺得簡單的事,否則只能是在浪費時間,對結果卻幫助不大。

 

(6)     模塊化設計程序,讓你有更方便的優化空間。

這點跟平時的編碼功底很有關係,混亂的設計雖然最後功能也實現了,但會讓你在優化的時候痛苦不堪,令結果也錯誤百出,最後不得不爲了保證正確性而不做優化。而清晰的程序設計,尤其是數據和執行邏輯的耦合性低,會讓你優化起來事半功倍。

 

(7)     記住,你程序的瓶頸也會因輸入數據的差異而轉變,所以不要忽略測試,並自己準備好最合理的測試數據。

關於這一點,也有不少感觸,用一開始提供的樣本數據,同最後公測的數據測試一比,發現瓶頸完全轉變了,所以記得多用不同的數據測測,並按重要程度做優化。

 

8) 當你需要頻繁申請、釋放內存的時候,可以考慮開闢一個內存池統一申請和釋放內存,因爲頻繁的new,delete也會花你很多時間,當然,前提還是你的程序是在ms級別上的優化。

 

三:其它感悟

(1)    公司能多舉辦這類比賽,確實能激發員工的活力,像我以前對圖形學,空間劃分,OpenMP這些知識一無所知,在短短數十天時間就接觸了大量相關知識並做優化,可以說能很大激發一個人的潛力,這讓我感覺特別刺激:)。

(2)    從應用OpenMP這個線程庫來看,其優點很明顯,結構化特性很強,但缺點也明顯,不適合開發大型程序,只能做局部優化,後面我會再去研究一下TBB怎麼用,待研究過幾套已有的線程庫之後,我想用ACELoki兩個C++庫來實現一個類似於OpenMP功能的線程庫,需要讓它能適應大規模程序開發。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章