Swifter C#之inline還是不inline,這是個問題

來源:互聯網
上載者:User

      如果問題是C#怎麼才能和C++一樣快,那麼真正的問題就是C#到底是慢在哪。內聯是諸多影響C#效能中的一個,如果頻繁調用的大量小函數沒有內聯,那麼對效能的影響是非常大的,因為建棧、刪棧、壓棧和跳轉的時間加起來很可能比實際執行函數體的時間還長。

 

      在實際的應用中,Milo Yip的《C++/C# /F#/Java/JS/Lua/Python/Ruby渲染比試》是非常好的例子,典型的計算密集的應用,裡面有大量向量計算的小函數調用。結果C#的表現令人失望,效能落後VC++版本一倍還多,即使我改成struct out ref的形式(代碼請參見Milo文章)雖然效能略有提高但是差距仍然較大。首先想到是否因為.NET CLR沒有內聯這些小函數導致的這個效能差異呢。實踐出真知,趕快調試看看,不知道如何看JIT產生的ASM的同學可以看Clayman的這篇文章。結果是我猜錯了,.NET的JIT編譯器已經內聯了這些函數。如下面向量按分量乘法的調用處:

 

                  Vec.mul(out rad, ref f, ref rad);
0000067e  fld         qword ptr [ebp-78h]
00000681  fmul        qword ptr [ebp+FFFFFF58h]
00000687  fstp        qword ptr [ebp+FFFFFF58h]
0000068d  fld         qword ptr [ebp-70h]
00000690  fmul        qword ptr [ebp+FFFFFF60h]
00000696  fstp        qword ptr [ebp+FFFFFF60h]
0000069c  fld         qword ptr [ebp-68h]
0000069f  fmul        qword ptr [ebp+FFFFFF68h]
000006a5  fstp        qword ptr [ebp+FFFFFF68h]

 

      看來並不是因為沒有內聯而造成的效能差異,不禁要深入思考下內聯的問題,一定不是所有的函數都會內聯的,那麼究竟.NET JIT內聯的規則是什麼呢。一定有比擲骰子更高明點的辦法。Google找到了一篇關於.NET CLR的內聯問題好文章,《Inline or not to Inline: That is the question》 博主Vance Morrison號稱是.NET Runtime的架構師,並且主要關注.NET Runtime的效能問題。聽起來很牛哦。以下是他的主要觀點:

 

      內聯並不總是好的,內聯的確會減少總的運行指令數。但是另一方面會增大代碼尺寸,這在代碼量比較大的時候可能會降低指令cache的命中率,如果L1 cache miss了需要從L2讀指令的情況會浪費3-10個刻度,而如果L2也Miss了需要從記憶體讀的話浪費的更多。而且更大的代碼尺寸會降低程式啟動的速度。.NET JIT取消了對於多大函數可以內聯的硬性規則,.NET項目組對應何種情況應該內聯做了大量實驗,JIT在決定是否進行inline是沒有足夠的資訊得知整個程式的運行流程,所以結果不會總是對的,但以下是顯而易見的:


      1.如果內聯減小了代碼的大小,那麼一定會內聯。注意我們說的尺寸是指機器碼(Native)的尺寸而不是IL代碼的尺寸。

      2.調用越頻繁的函數越可能被內聯從而得到更好的效能,比如在迴圈內的調用比迴圈外的內聯的機會更大。

      3.內聯可能帶來更好的最佳化的情況更可能被內聯,比如實值型別參數的函數更可能被內聯,因為內聯實值型別參數的函數通常可以帶來更好的最佳化效果。


      JIT採用如下啟發學習法演算法來進行判斷


      1.評估非內聯情況下的調用體大小。

      2.評估在內聯情況下的調用體大小,這個評估是基於IL的,我們用一個簡單的狀態機器(Markov Model,猜測是隱式馬爾科夫模型),其中使用的評估邏輯基於大量的實測資料。

      3.計算一個係數。預設是1.

      4.如果代碼在迴圈裡增加係數。(5x)

      5.(原文:Increase the multiplier if it looks like struct optimizations will kick in). 沒太明白是結構性的最佳化還是指實值型別中的struct。

      6.如果 內聯的大小 <= 不內聯的大小 * 係數  則進行內聯


      結論很簡單:


      1.內聯對C#來說是透明的JIT會搞定的,要相信組織。

      2.小的函數更容易被內聯。因為內聯後不會顯著增大代碼尺寸。

      3.在迴圈體內的函數調用更容易被內聯。

      4.使用實值型別參數的函數更容易被內聯。

 

      對於上面的觀點我進行了驗證,結果如下:

 

      1.的確實際情況中同一個函數在迴圈內一般會內聯而外面不會。如同樣的向量normal()函數。     

 

public static void mul(out Vec result, ref Vec a, ref Vec b){    result.x = a.x * b.x;    result.y = a.y * b.y;    result.z = a.z * b.z;}public void normal(){    mul(out this, ref this, 1 / Math.Sqrt(x * x + y * y + z * z));}

 

 

 

      A情況沒有內聯:調用在主函數開頭,即整個程式只會運行一次:

                  rd.normal();
0000007d  lea         ecx,[ebp-40h]
00000080  call        dword ptr ds:[00143978h]

 

      B情況內聯了:調用在radiance函數中,而radiance在主函數的多次迴圈內:

                  u.normal();
000003e9  fld         qword ptr [ebp+FFFFFF28h]
000003ef  fmul        st,st(0)
000003f1  fld         qword ptr [ebp+FFFFFF30h]
000003f7  fmul        st,st(0)
000003f9  faddp       st(1),st
000003fb  fld         qword ptr [ebp+FFFFFF38h]
00000401  fmul        st,st(0)
00000403  faddp       st(1),st
00000405  fsqrt
00000407  fld1
00000409  fdivrp      st(1),st
0000040b  fld         st(0)
0000040d  fmul        qword ptr [ebp+FFFFFF28h]
00000413  fstp        qword ptr [ebp+FFFFFF28h]
00000419  fld         st(0)
0000041b  fmul        qword ptr [ebp+FFFFFF30h]
00000421  fstp        qword ptr [ebp+FFFFFF30h]
00000427  fmul        qword ptr [ebp+FFFFFF38h]
0000042d  fstp        qword ptr [ebp+FFFFFF38h] 

 

可見的確在迴圈體內的函數更可能被inline,而且normal函數是比較大的。所以是否內聯得看調用情況,直接調用一個函數看是否內聯是不行的。

 

2.我測試了.NET 4 CP和.NET 3.5 2.0的情況,發現JIT內聯產生的程式碼是不一樣的。如上面的mul函數的同一處調用為例:

在 .NET 2.0、3.0、3.5下產生的程式碼

                  Vec.mul(out x, ref r.d, t);
000000db  lea         ecx,[esp+10h]
000000df  lea         edx,[ebp+8]
000000e2  cmp         byte ptr [edx],al
000000e4  add         edx,18h
000000e7  mov         eax,edx
000000e9  fld         qword ptr [esp]
000000ec  fstp        qword ptr [esp+000003B0h]
000000f3  fld         qword ptr [eax]
000000f5  fmul        qword ptr [esp+000003B0h]
000000fc  fstp        qword ptr [ecx]
000000fe  fld         qword ptr [eax+8]
00000101  fmul        qword ptr [esp+000003B0h]
00000108  fstp        qword ptr [ecx+8]
0000010b  fld         qword ptr [eax+10h]
0000010e  fmul        qword ptr [esp+000003B0h]
00000115  fstp        qword ptr [ecx+10h]

 

 在.NET 4下產生的程式碼
                  Vec.mul(out x, ref r.d, t);
000000d2  fld         qword ptr [ebp-14h]
000000d5  lea         eax,[ebp+20h]
000000d8  fld         qword ptr [eax]
000000da  fmul        st,st(1)
000000dc  fstp        qword ptr [ebp-30h]
000000df  lea         eax,[ebp+20h]
000000e2  fld         qword ptr [eax+8]
000000e5  fmul        st,st(1)
000000e7  fstp        qword ptr [ebp-28h]
000000ea  lea         eax,[ebp+20h]
000000ed  fld         qword ptr [eax+10h]
000000f0  fmulp       st(1),st
000000f2  fstp        qword ptr [ebp-20h] 

 

      clr 4.0和2.0產生的程式碼是不同的,而且.NET 4 JIT產生的內聯代碼效率更高,這也許可以解釋為什麼這個測試程式在3.5和4裡面有較大效能差異,3.5用時86秒,4用時67秒。我仔細查看了測試程式中的調用,在迴圈內被頻繁調用的計算函數都被內聯了,只有在迴圈外只運行一次的沒有被內聯,看來JIT工作的很好,我們可以放心的把inline的工作交給JIT了。

      既然不是內聯導致的效能問題那麼造成C#這個測試效能不佳的原因還有什麼呢,是因為C#的兩次編譯無法進行C++那樣的更深入全面的最佳化嗎,還是因為其他原因呢?我們還需要繼續去探索。

      To be continued. . .

代碼

                      Vec.mul(out rad, ref f, ref rad);
0000067e fld qword ptr [ebp-78h]
00000681 fmul qword ptr [ebp+FFFFFF58h]
00000687 fstp qword ptr [ebp+FFFFFF58h]
0000068d fld qword ptr [ebp-70h]
00000690 fmul qword ptr [ebp+FFFFFF60h]
00000696 fstp qword ptr [ebp+FFFFFF60h]
0000069c fld qword ptr [ebp-68h]
0000069f fmul qword ptr [ebp+FFFFFF68h]
000006a5 fstp qword ptr [ebp+FFFFFF68h]

 

http://www.cnblogs.com/miloyip/archive/2010/07/07/languages_brawl_GI.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.