内联函数的32字节限制...不是太小?

时间:2016-12-11 22:37:10

标签: c# inline

我有一个非常小的c#代码标记为内联,但不起作用。 我已经看到最长的函数生成超过32个字节的IL代码。 32字节的限制是否太短?

// inlined
[MethodImpl(MethodImplOptions.AggressiveInlining)] 
static public bool INL_IsInRange (this byte pValue, byte pMin) {
  return(pValue>=pMin);
}

// NOT inlined
[MethodImpl(MethodImplOptions.AggressiveInlining)] 
static public bool INL_IsInRange (this byte pValue, byte pMin, byte pMax) {
  return(pValue>=pMin&&pValue<=pMax);
}

是否可以更改该限制?

2 个答案:

答案 0 :(得分:1)

我也在寻找内联函数标准。在您的情况下,我相信JIT优化会在它决定内联您的第二个功能之前超时。对于JIT,它不是内联函数的优先级,因此它正在忙于分析您的长代码。但是,如果将调用置于紧密循环中,JIT可能会内联它们,因为内部调用优先于内联调用。如果您真的关心这种类型的微优化,那么现在是时候切换到C ++了。它是一个全新的勇敢世界,供您探索和利用!

我注意到这个问题是在这个答案发布后立即编辑的,这意味着高水平的互动性。好吧,我不知道为什么有32个字节的限制,但保守地说,这似乎正好是CPU缓存块的大小。真是巧合!在任何情况下,代码优化必须使用特定的硬件配置完成,最好与其程序集并排保存在额外的文件中。超时策略是愚蠢的,因为优化不应该在运行时完成,与宝贵的代码执行时间竞争。优化应该在应用程序加载时完成,只有它第一次在机器上运行,一次性完成。检测到硬件配置更改时,可以再次触发它。再说一次,如果你真的需要性能,那就去C / C ++吧。 C#不是为性能而设计的,永远不会将性能作为首要任务。与Java一样,C#是为安全而设计的,对可能产生的负面性能影响要小心谨慎。

答案 1 :(得分:1)

直到“ IL的32字节”限制,还有许多其他因素会影响方法是否内联。至少有几篇文章描述了这些因素。

One article解释说,内联与不内联(即,呼叫站点是大于还是小于内联代码本身),使用计分启发法来调整有关代码相对大小的初始猜测:

  
      
  1. 如果内联使代码更小然后替换它的调用,那就总是好的。请注意,我们在谈论的是NATIVE代码的大小,而不是IL代码的大小(可能会大不相同)。

  2.   
  3. 执行特定呼叫站点的次数越多,则从inlning中受益越多。因此,循环代码比非循环代码更应该内联。

  4.   
  5. 如果内联公开了重要的优化,则内联更为可取。在具有值类型的方法中,参数尤其比常规方法受益更多,这是因为这样的优化,因此倾向于内联这些方法是很好的。

  6.   
     

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

     
      
  1. 如果未内联方法,请估算呼叫站点的大小。

  2.   
  3. 如果内联呼叫站点,则估计其大小(这是基于IL的估计,我们使用一个简单的状态机(马尔可夫模型),使用大量真实数据创建该状态机以形成此估计器逻辑)

  4.   
  5. 计算一个乘数。默认为1

  6.   
  7. 如果代码处于循环中,则增加乘数(当前试探法将其乘以5即可)

  8.   
  9. 如果看起来像是结构优化将要增加乘数。

  10.   
  11. 如果InlineSize <= NonInlineSize *乘数进行内联。

  12.   

Another article解释了一些条件,这些条件将阻止仅基于它们存在的方法(包括“ IL的32字节”限制)进行内联:

  

以下是一些我们无法内联方法的原因:

     
      
  • 方法被标记为不与CompilerServices.MethodImpl属性内联。

  •   
  • 内联函数的大小限制为IL的32个字节:这是一种启发式方法,其背后的原理是通常,当您拥有的方法大于该方法时,与之相比,调用的开销将不那么有意义。该方法完成的工作。当然,作为一种启发式方法,它在某些情况下会失败。有人建议我们添加一个属性来控制这些阈值。对于Whidbey而言,尚未添加该属性(它具有一些非常糟糕的属性:它是x86 JIT特定的,并且随着编译器变得越来越聪明,它的长期价值值得怀疑)。

  •   
  • 虚拟电话:我们不会在虚拟电话中内联。不这样做的原因是我们不知道通话的最终目标。我们在这里可能会做得更好(例如,如果99%的调用最终都在同一目标中,则可以生成代码来检查将要在其上执行的虚拟调用的对象的方法表,如果不是)在99%的情况下,您进行调用,否则您仅执行内联代码),但是与J语言不同,我们支持的主要语言中的大多数调用都不是虚拟的,因此我们不必被迫如此积极优化这种情况。

  •   
  • 值类型:关于内联的值类型,我们有一些限制。我们在这里指责,这是我们JIT的局限性,我们可以做得更好,我们知道这一点。不幸的是,当堆栈与Whidbey的其他功能进行排名时,获得了一些统计信息,说明由于该原因无法内联方法的频率,并考虑了使JIT的这一区域显着改善的成本,我们认为这对我们的客户来说更有意义花时间在其他优化或CLR功能上。 Whidbey在一种情况下比以前的版本要好:只将int作为成员的指针大小为int的值类型,使其变好(相对)并不昂贵,并且帮助了很多常见的值类型,例如指针包装(IntPtr等) )。

  •   
  • MarshalByRef:不会内联MarshalByRef类中的调用目标(必须拦截并调度调用)。在这种情况下,我们在惠德比(Whidbey)变得更好了

  •   
  • VM限制:这些主要是安全性,JIT必须要求VM允许内联方法(请参见Rotor源代码中的CEEInfo :: canInline以了解VM检查的内容)。

  •   
  • 复杂的流程图:我们不内联循环,具有异常处理区域的方法等...

  •   
  • 如果认为具有调用的基本块不会被频繁执行(例如,具有引发的基本块或静态类构造函数),则内联的攻击性要低得多(作为唯一方法)我们可以取得的真正胜利是代码大小)

  •   
  • 其他:外来的IL指令,需要方法框架的安全检查等...

  •