JIT拒绝内联微小的方法

时间:2014-05-24 21:57:04

标签: c# .net optimization inline 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有一个电话,这是绝对错误的。为什么这么小的方法不适合内联?对于注释,这是通过优化编译的。

5 个答案:

答案 0 :(得分:8)

除了使用提前的源代码或字节码转换在指令到达JIT之前内联指令之外,没有办法要求JIT编译器内联您的方法。

如果您的算法对微优化非常敏感,那么删除调用指令会带来显着的性能优势,那么您可以考虑用不同的语言重写代码的性能关键部分,从而为控制该行为提供更广泛的工具。根据您的问题的措辞,您似乎试图将C#强制进入一个设计为完全避免的问题空间。

答案 1 :(得分: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/

答案 2 :(得分:4)

.Net的抖动具有内置的启发式功能,有助于确定To Inline or not to Inline 。因为我找不到一个阻止内联的好理由(见下文),并且在4.5中可以通过AggressiveInlining说服它,所以抖动可以内联,如果它想要的话,可能就是这样。引用:

  
      
  1. 如果内联使代码变小,那么它取代的调用就会很好。请注意,我们讨论的是NATIVE代码大小,而不是   IL代码大小(可能完全不同)。

  2.   
  3. 执行特定呼叫站点的次数越多,它就越有利于inlning。因此,循环中的代码应该更多地内联   而不是循环中的代码。

  4.   
  5. 如果内联公开了重要的优化,那么内联是更理想的。特别是具有值类型参数的方法   由于这样的优化,因此比正常受益更多   偏向内联这些方法是好的。

  6.         

    因此,给定内联的X86 JIT编译器使用的启发式算法   候选者。

         
        
    1. 如果方法未内联,请估算调用网站的大小。

    2.   
    3. 估计呼叫站点的大小(如果它是内联的)(这是基于IL的估计,我们使用简单的状态机(Markov)   模型),使用大量实际数据创建,形成此估算逻辑)

    4.   
    5. 计算乘数。默认情况下为1

    6.   
    7. 如果代码处于循环中,则增加乘数(当前启发式循环将其变为5)

    8.   
    9. 如果看起来结构优化会起作用,请增加乘数。

    10.   
    11. 如果InlineSize&lt; = NonInlineSize * Multiplier执行内联。

    12.   

以下是对我尝试深究这一点的描述,它可能会在类似的情况下帮助其他人。

我可以在.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上)时,不仅内联调用,而且内联代码完全被删除 - 因为它应该,因为你不# 39; t使用返回值:

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

N.B。我不确定您是否知道除了使用发布构建模式之外,您have to

  
      
  • 转到工具=&gt;选项=&gt;调试=&gt;常规并确保标记为“在模块加载时抑制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 

即使没有AggressiveInlining,以下更短(而非等效)的btw方法也会被内联:

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

答案 3 :(得分:3)

当您进行微优化时,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字节,这可能会影响抖动决定是否内联。

答案 4 :(得分:1)

Visual Studio 2017(及更早版本)调试器选项中有一个名为“Suppress JIT optimization on module load (Managed Only)”的复选框。如果选中此选项,则无论 Release Debug build还是[MethodImpl(MethodImplOptions.AggressiveInlining)]属性,都不会在调试器中运行任何方法内联。


enter image description here