• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            歲月流轉(zhuǎn),往昔空明

            C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
              118 Posts :: 3 Stories :: 413 Comments :: 0 Trackbacks

            梗概

            SALVIA 0.5.2 的優(yōu)化經(jīng)歷是一個“跌宕起伏”的過程。這個過程的結(jié)果很簡單:

            在Core 2 Duo T5800(2.0GHz x 2)上,Sponza的性能提升了60%,ComplexMesh性能提升了26%。

             

            背景

            SALVIA的整個渲染流程主要是以下幾部分:

            • 根據(jù)Index Buffer獲得需要進(jìn)行變換的頂點;
            • 將頂點利用Vertex Shader進(jìn)行變換;
            • 將變換后的頂點,輸出成若干個float4;
            • 將三角形光柵化。SALVIA的光柵化是將三角形拆分成4x4的像素塊若干,不滿的塊有掩碼來處理;
            • 將像素進(jìn)行插值;
            • 插完值后把像素送到Pixel Shader中處理一趟;
            • 處理完的結(jié)果用Blend Shader塞到Back buffer里面去。

            用于測試的場景:

            • Sponza 26萬個面,20個左右的Diffuse紋理(1024x1024);
            • PartOfSponza 約200個面,4個Diffuse紋理(1024x1024);
            • ComplexMesh 兩萬個面,無紋理,有個能量保守的光照。

            最初的版本(V1231)中,性能的主要瓶頸在插值階段,各種耗時林林總總占了一半以上(50% - 70%)。

            相比之下其他階段對性能的影響要么有限,要么沒有多少優(yōu)化空間。所以最近一周的優(yōu)化,就都集中在了“插值”上。

             

            插值算法

            線性的插值算法常見的實現(xiàn)有兩種,

            第一種是拿UV插值,第二種是用ddx和ddy累積。

            UV是先計算像素的u和v(基本方法是用面積比,不記得就復(fù)習(xí)一下中學(xué)幾何吧),然后用插值公式:

            pixel = v0 * u + v1 * v + v2 * (1-u-v)

            后者的步驟是選一個主頂點,然后計算這個頂點的ddx和ddy,最后用

            pixel = v0 + ddx * offset_x + ddy * offset_y

            計算出相應(yīng)頂點。

            但是在圖形學(xué)中,我們還需要對插值進(jìn)行透視修正,獲得在3D空間中線性的插值結(jié)果。

            我們將步驟修正到透視空間

            先將v0,v1,v2弄到透視空間中,變成projected_v0, projected_v1, projected_v2

            對于UV的插值是

            pixel = ( projected_v0*u + projected_v1*v + projected_v2 * (1-u-v) ) / pixel_w

            對于用ddx和ddy的累積公式是:

            pixel = ( projected_v0 + projected_ddx * offset_x + projected_ddy * offset_y ) / pixel_w

             

            插值算法的選擇

            何詠(Graphixer)大神之前也寫了一個渲染器,比我快許多(大概是4-6倍),用的是UV;

            gameKnife大神兩個禮拜寫成的渲染器,速度比我用五年寫出來的半成品要快7倍,用的辦法是Lerp到Scanline上,再Lerp到像素。

            SALVIA采用了累積法:

            struct transformed_vertex { float4 attributes[MAX_ATTRIBUTE_COUNT]; };
            transformed_vertex projected_corner;
            
            // 計算角點的坐標(biāo)
            projected_scanline_start = projected_v0 + projected_ddx * offset_x + projected_ddy * offset_y; 
            
            // 像素的透視修正值
            float inv_w; 
            
            // 最終輸出的4x4個像素
            pixel_input px_in[4][4];
            
            for(int i = 0; i < 4; ++i)
            {
              projected_pixel = projected_scanline_start;
              for(int j = 0; j < 4; ++j)
              {
                  // 透視空間轉(zhuǎn)換到線性空間并輸出到px_in中
                  px_in[i][j] = unproject( projected_pixel );
                 // 累加x方向上的值(透視空間)
                  projected_pixel += projected_ddx;
              }
              // 累加y方向上的值(透視空間)
              projected_scanline_start += projected_ddy;
            }

             

            本輪優(yōu)化之前對插值算法的優(yōu)化嘗試

            注意那個MAX_ATTRIBUTE_COUNT,這個值通常比較大,在v1231中,它是32。

            不過,顯然我們不需要對所有的屬性進(jìn)行計算。敏敏在這里運用了一點小小的技巧進(jìn)行了優(yōu)化:只計算必要的屬性。同時,為了減少分支的使用,他甚至用

            template <int N>
            void sub_n(out, v0, v1 )
            {
                for(int i = 0; i < N; ++i) {
                   out.attributes[i] = v0.attributes[i] – v1.attributes[i];
                }
            }

            并配合函數(shù)指針的方法,以促使編譯器展開循環(huán),減少分支。

            不過從實際生成的匯編來看,這個部分并沒有被展開到期望的形式,可能是編譯器認(rèn)為x86的Branch Predication性能已經(jīng)足夠高了吧。

            這個“優(yōu)化”在v1231中就已經(jīng)具備了。

             

            首輪優(yōu)化:unproject函數(shù),operator += 與 operator =

            第一個Profiling是用BenchmarkPartOfSponza和Sponza跑的;unproject,operator +=和operator = 加在一起大約占用了15-20%的時間。單獨的unproject

            最初的實現(xiàn)就是普通的標(biāo)量。既不要求對齊,也沒有使用SIMD。

            所以當(dāng)然會以為用了SIMD后,優(yōu)化效果會很好。于是在v1232中,中間頂點和像素輸入的分配都以16字節(jié)對齊,unproj,+=和=也都使用了SSE進(jìn)行了重寫。

            從跑分來看,PartOfSponza性能提升了20%。但是,在測試ComplexMesh和Sponza時,并未發(fā)現(xiàn)幀率有顯著提升。

            其實在進(jìn)行優(yōu)化之前,何詠就告誡過我,因為現(xiàn)代CPU的一些技術(shù),比方說超標(biāo)量啥的,四個數(shù)據(jù)寬度的SSE和標(biāo)量運算相比,就只有50%的性能差距。

            并且這些函數(shù)的指令已經(jīng)極為簡單,瓶頸也很明確的落在計算指令上。例如Unproject優(yōu)化后,性能焦點就落在_mm_mul_ps上(3.7%),幾無優(yōu)化余地。

             

            二輪優(yōu)化:插值算法的調(diào)整

            在進(jìn)行第二輪優(yōu)化之前同樣運行了一次Profiling。因為對PartOfSponza性能基本滿意,因此這次優(yōu)化的目標(biāo)主要在Sponza上。

            排名前幾位的小函數(shù),分別是sub_n,unproj,+= 和tex2D。對sub_n例行優(yōu)化后,性能沒什么變化。當(dāng)然,這也是意料之中的事情了。

            因此,第二輪優(yōu)化便著重考慮在插值算法本身上。

            在優(yōu)化之前,我嘗試對代碼成本做個粗略的評估:

            在現(xiàn)有算法下,假設(shè)每個像素有N個需要插值的屬性,則平均每個像素有

            (corner)3N/16個讀 + 2N/16個乘法 + 2N/16個加法 + N/16個寫

            (x:+=)2N個讀 + N個加法 + N個寫

            (x:*)  N個讀 + 1個標(biāo)量除法 + N個乘法 + N個寫

            (y:+=)2N/4個讀 + N/4個加法 + N/4個寫

            (y:=) N/4個讀 + N/4個寫

            因為每個都是函數(shù)指針,所以這些都是優(yōu)化不掉的。因此首先將一些操作合并了一下,比如把+= 和*合并以減少一下讀寫操作。只可惜效果也不是很明顯。

             

            第二刀就砍到算法的頭上。因為累加本身是為了減少乘法的運用,但是這可能帶來了多余的存取開銷。

            因此直接套用公式:

            pixel = ( projected_v0 + projected_ddx * offset_x + projected_ddy * offset_y ) / pixel_w

            這樣就有:3N讀,2N乘法,2N加法,N個乘法和N個寫(假設(shè)寄存器夠用的話)。不算Corner的計算成本,這樣比較一下,就等于是3N/4個讀,N/2+N個寫,N/4個加法來換取2N個乘法的時間。本來以為作為IO瓶頸的應(yīng)用,這樣可以提高一些性能。不過結(jié)果證實這個買賣實在是很不劃算,整體性能不增反減。

             

            三輪優(yōu)化:減少內(nèi)存占用,柳暗花明

            雖然所有的操作只針對已使用的屬性,但是空間上還是浪費了許多。

            考慮到內(nèi)存占用較大也會導(dǎo)致一些性能損失,于是將MAX_ATTRIBUTE_COUNT從32下調(diào)到了8。

            結(jié)果令人大跌眼鏡。性能瞬間提升了20-30%之多。

            再加上SSE也不知道為什么開始發(fā)力了,使用上之后性能大約又有了10-15%的提升。

            我猜測可能是因為換頁頻率下降,以及Cache的命中率提升。不過手上沒有VTune這種工具,所以也不太好驗證。

             

            四輪優(yōu)化:精度敏感性下降的額外紅利

            在這輪優(yōu)化之后,PartOfSponza出現(xiàn)了精度問題。因為視錐體的上下左右四個面都沒有Clip,所以可能會出現(xiàn)非常大的三角形。這樣累積的時候一旦起始點選擇的不好,就會出現(xiàn)比較大的誤差。在之前版本中,使用/fp: precise來減少這一問題出現(xiàn)的機會。但是因為使用了SSE,也讓這個問題再難解決。因此我選用了一些辦法,來改善精度問題。在大問題都修正以后,換用/fp: fast來編譯整個SALVIA,最終也獲得了0-10%左右的性能收益。

             

            結(jié)論

            對于運算和IO都密集的程序來說,優(yōu)化真可能是牽一發(fā)而動全身的問題。比如在我的例子中,所有猜測是性能瓶頸的地方,都沒有得到預(yù)想中的改善。

            倒是在內(nèi)存占用這個地方無心插柳,才得以柳暗花明,而且還讓別的優(yōu)化方案體現(xiàn)了價值。所以如果你不像qiaojie大牛那樣對x86了如指掌,還是要習(xí)慣于從多方面猜測,例如內(nèi)存占用,對齊或緊縮,計算強度,訪存密度,并行度等多個角度進(jìn)行設(shè)想并用實踐去驗證。盡管可能會遇到很多挫折,但是,只要是直覺上有優(yōu)化的余地,一般都可以找到合適的方案。

            posted on 2013-02-11 20:09 空明流轉(zhuǎn) 閱讀(2809) 評論(2)  編輯 收藏 引用

            評論

            # re: SALVIA 0.5.2優(yōu)化談 2013-02-15 23:56 egmkang
            跪拜巨巨  回復(fù)  更多評論
              

            # re: SALVIA 0.5.2優(yōu)化談 2014-02-17 23:17 Kitchen020
            一直有SALVIA的代碼,最近開始研究,LZ繼續(xù)努力!!  回復(fù)  更多評論
              


            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            国产一久久香蕉国产线看观看| 亚洲精品国精品久久99热一| 秋霞久久国产精品电影院| 国产农村妇女毛片精品久久| 中文成人久久久久影院免费观看| 日产精品久久久久久久| 久久精品国产亚洲av瑜伽| 久久精品国产AV一区二区三区| 狠狠色婷婷久久一区二区三区| 中文字幕亚洲综合久久2| 精品国产乱码久久久久久人妻| 亚洲狠狠综合久久| 婷婷伊人久久大香线蕉AV| 久久精品三级视频| 久久综合噜噜激激的五月天| 亚洲精品无码久久久| 久久99精品综合国产首页| 亚洲va中文字幕无码久久| 久久久久无码精品| 91精品国产91热久久久久福利 | 精品久久久久中文字| 日产精品久久久久久久性色| 国产亚洲色婷婷久久99精品91| 亚洲AV无码一区东京热久久| 久久精品国产男包| 久久久久综合国产欧美一区二区| 狠狠狠色丁香婷婷综合久久五月| 日本WV一本一道久久香蕉| 色综合久久天天综线观看| 欧美久久综合性欧美| www.久久热.com| 国产一级做a爰片久久毛片| AAA级久久久精品无码片| 婷婷综合久久中文字幕蜜桃三电影| 久久综合九色综合网站| 久久久久久亚洲精品无码| 久久久精品久久久久特色影视| 国产精品gz久久久| 久久天天日天天操综合伊人av| 久久精品国产欧美日韩| 中文字幕精品久久久久人妻|