2014-05-24 55 views
2

我失去了嚴重的優化,因爲JIT不會內嵌了很多我的方法。JIT拒絕內聯微小的方法

例如讓有下面的代碼:

static void Main(string[] args) 
{ 
    IsControl('\0'); 
} 

public static bool IsControl(char c) 
{ 
    return ((c >= 0 && c <= 31) || (c >= 127 && c <= 159)); 
} 

JIT編譯後生成以下:

0000001f xor   ecx,ecx 
00000021 call  FFFFFFFFFFEC9760 
00000026 mov   byte ptr [rsp+20h],al 
0000002a nop 
0000002b jmp   000000000000002D 
0000002d add   rsp,38h 
00000031 rep ret 

注意0000001f是我設置的斷點。正如你所看到的,在00000021有一個電話,那是絕對錯誤的。爲什麼這麼小的方法不適合內聯?對於說明,這是在優化的基礎上編譯的。

+3

你跟一個發佈版本檢查嗎?因爲在Debug中,編譯器將保留所有的功能,以便調試應用程序。 – MicroVirus

+0

@Micro是的,我做 –

+0

只是清除明顯的:)當IsControl是私人的時候會發生同樣的事情嗎? – MicroVirus

回答

8

沒有辦法要求JIT編譯器使用名列前茅的時間源或字節碼轉換到內聯說明他們曾經達到JIT內聯之前,你的方法,一邊。

如果您的算法對微優化非常敏感以至於刪除調用指令會帶來顯着的性能優勢,那麼您可能會考慮用不同語言重寫代碼中性能至關重要的部分,從而提供更廣泛的工具來控制該行爲。根據你的問題的措辭,似乎你試圖強制C#進入一個問題空間,它被設計爲完全避免。

+0

我想這是我必須處理的。 –

5

使用MethodImplAttribute屬性:

[MethodImpl(MethodImplOptions.AggressiveInlining)] 
public static bool IsControl(char c) 
{ 
    return ((c >= 0 && c <= 31) || (c >= 127 && c <= 159)); 
} 

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.methodimplattribute.aspx

http://blogs.microsoft.co.il/sasha/2012/01/20/aggressive-inlining-in-the-clr-45-jit/

+0

這是真正的答案。 –

+0

我使用.NET 3.5,所以這不是一個真正的選擇。 –

2

NET的抖動已經內置啓發,他LP決定是否To Inline or not to Inline。由於我無法找到阻止內聯的一個很好的理由(見下文),並且在4.5中可以通過AggressiveInlining來說服它,所以如果願意,抖動可以內聯,可能就是這樣。引述:

  1. 如果內聯使代碼更小,則調用它取代了,這是一件好事。請注意,我們正在討論的是NATIVE代碼的大小,而不是IL代碼的大小(這可能會非常不同)。

  2. 執行更特定的呼叫現場,更將受益於inlning。因此循環中的代碼應該比不在循環中的代碼更內聯 。

  3. 如果內聯暴露出一些重要的優化,那麼內聯是更加希望的。具體來說,值類型參數 的方法會因爲這樣的優化而受益比正常情況更多,因此 有內聯這些方法的偏好是很好的。

因此,X86 JIT編譯器使用啓發式是,給定的一個內聯 候選。

  1. 如果方法未內聯,估計呼叫站點的大小。

  2. 估計呼叫站點的大小(如果它是內聯的)(這是基於IL的估計,我們使用一個簡單的狀態機(Markov 模型),使用大量實際數據創建該估計器邏輯)

  3. 計算乘數。默認情況下,它是1

  4. 增加乘數如果代碼是在一個循環(當前啓發式它對顛簸到5環)

  5. 增加倍數,如果它看起來像結構優化將一命嗚呼。

  6. If InlineSize < = NonInlineSize * Multiplier做內聯。


下面是我嘗試去的這條底線的描述,它可以幫助其他人在類似的情況。

我可以在.Net 4.5(x68和x64)上重現它,但是我不知道爲什麼它沒有被內聯,因爲它沒有像inlining show stoppers那樣的虛擬方法或者消耗多於32個字節。這是30個字節的短:

.method public hidebysig static bool IsControl(char c) cil managed 
{ 
    // code size  30 (0x1e) 
    .maxstack 8 
    IL_0000: ldarg.0 
    IL_0001: ldc.i4.0 
    IL_0002: blt.s  IL_0009 
    IL_0004: ldarg.0 
    IL_0005: ldc.i4.s 31 
    IL_0007: ble.s  IL_001c 
    IL_0009: ldarg.0 
    IL_000a: ldc.i4.s 127 
    IL_000c: blt.s  IL_001a 
    IL_000e: ldarg.0 
    IL_000f: ldc.i4  0x9f 
    IL_0014: cgt 
    IL_0016: ldc.i4.0 
    IL_0017: ceq 
    IL_0019: ret 
    IL_001a: ldc.i4.0 
    IL_001b: ret 
    IL_001c: ldc.i4.1 
    IL_001d: ret 
} // end of method Program::IsControl 

當啓用AggressiveInlining(你說你不能,因爲你是對的.Net 3.5),則不僅調用get內聯,但聯的代碼被徹底忽略, - 因爲它不應該使用返回值:

--- Program.cs -------------------------------------------- 
     IsControl('\0'); 
00000000 ret 

NB我不知道,如果你知道,除了使用發佈版本模式,你have to

  • 選擇工具=>選項=>調試=>常規,並確保盒標有「禁止JIT優化模塊加載「未選中。
  • 確保未選中標記爲「啓用我的代碼」的框。

爲了看到JIT優化的代碼。如果不這樣做,你會得到以下代替上述單ret聲明:

--- Program.cs -------------------------------------------- 
     IsControl('\0'); 
00000000 push  rbp 
00000001 sub   rsp,30h 
00000005 lea   rbp,[rsp+30h] 
0000000a mov   qword ptr [rbp+10h],rcx 
0000000e mov   rax,7FF7F43335E0h 
00000018 cmp   dword ptr [rax],0 
0000001b je   0000000000000022 
0000001d call  000000005FAB06C4 
00000022 xor   ecx,ecx 
00000024 call  FFFFFFFFFFFFD3D0 
00000029 and   eax,0FFh 
0000002e mov   dword ptr [rbp-4],eax 
00000031 nop 
    } 
00000032 nop 
00000033 lea   rsp,[rbp] 
00000037 pop   rbp 
00000038 ret 

下,短(而不是等同)方法BTW將獲得內聯即使沒有AggressiveInlining

public static bool IsControl(char c) 
{ 
    return c <= 31 || c >= 127; 
} 
+0

你可以在COMPLUS下運行它,看看爲什麼編譯器決定不內聯它。請參閱https://github.com/dotnet/coreclr/blob/master/Documentation/building/viewing-jit-dumps.md –

2

當你進入微的優化,你的IsControl方法應該如下所示的任何一個,這取決於c值(預期)實際分佈:

public static bool IsControl2(char c) 
{ 
    return c <= 31 || (c >= 127 && c <= 159); 
} 
public static bool IsControl3(char c) 
{ 
    return c <= 159 && (c <= 31 || c >= 127); 
} 

它將刪除多餘的支票c >= 0minimum value of char is 0),在最壞的情況下將比較次數減少到3次(儘管我沒有檢查抖動是否足夠聰明以避免冗餘檢查),並且它也將該方法的代碼大小從30個減少到26個字節,這可能影響由抖動決定是否內聯。