【问题标题】:Can I force the compiler to optimize a specific method?我可以强制编译器优化特定方法吗?
【发布时间】:2012-03-29 18:22:16
【问题描述】:

是否有一个属性可以告诉编译器必须始终优化方法,即使未设置全局 /o+ 编译器开关?

我问的原因是因为我正在玩弄基于现有方法的 IL 代码动态创建方法的想法;当代码被优化时,我想要做的操作相当容易,但在未优化的代码中变得非常困难,因为编译器生成了额外的指令。


编辑:有关困扰我的非优化的更多详细信息...

让我们考虑阶乘函数的以下实现:

static long FactorialRec(int n, long acc)
{
    if (n == 0)
        return acc;
    return FactorialRec(n - 1, acc * n);
}

(注意:我知道有更好的方法来计算阶乘,这只是一个例子)

启用优化后生成的 IL 非常简单:

IL_0000:  ldarg.0     
IL_0001:  brtrue.s    IL_0005
IL_0003:  ldarg.1     
IL_0004:  ret         
IL_0005:  ldarg.0     
IL_0006:  ldc.i4.1    
IL_0007:  sub         
IL_0008:  ldarg.1     
IL_0009:  ldarg.0     
IL_000A:  conv.i8     
IL_000B:  mul         
IL_000C:  call        UserQuery.FactorialRec
IL_0011:  ret         

但是未优化的版本就大不一样了

IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  ceq         
IL_0005:  ldc.i4.0    
IL_0006:  ceq         
IL_0008:  stloc.1     
IL_0009:  ldloc.1     
IL_000A:  brtrue.s    IL_0010
IL_000C:  ldarg.1     
IL_000D:  stloc.0     
IL_000E:  br.s        IL_001F
IL_0010:  ldarg.0     
IL_0011:  ldc.i4.1    
IL_0012:  sub         
IL_0013:  ldarg.1     
IL_0014:  ldarg.0     
IL_0015:  conv.i8     
IL_0016:  mul         
IL_0017:  call        UserQuery.FactorialRec
IL_001C:  stloc.0     
IL_001D:  br.s        IL_001F
IL_001F:  ldloc.0     
IL_0020:  ret         

它被设计为只有一个出口点,最后。要返回的值存储在局部变量中。

为什么这是一个问题?我想动态生成一个包含尾调用优化的方法。通过在递归调用之前添加tail. 前缀可以轻松修改优化的方法,因为在调用之后除了ret 之外没有任何内容。但是对于未优化的版本,我不太确定......递归调用的结果存储在局部变量中,然后有一个无用的分支,只是跳转到下一条指令,局部变量被加载并返回。所以我没有简单的方法来检查递归调用是否真的是最后一条指令,所以我不能确定是否可以应用尾调用优化。

【问题讨论】:

  • AFAIK,不 - 这是不可能的
  • JIT 编译器总是会优化每一个方法。
  • @Steven,如果你告诉它不要这样做(例如,MethodImplAttribute 中的 NoOptimization 标志)。但无论如何,我的问题是关于编译器优化,而不是 JIT 优化,因为我对生成的 IL 代码感兴趣。
  • 我希望看到您记录您遇到问题的确切类型的 IL。在 C# 编译器中打开优化几乎没有影响 afaik,它只是省略了编译器发出的 NOP 操作码以使调试更容易。我能想到的唯一实用的解决方案是将代码移动到一个单独的程序集中,然后您可以在启用优化的情况下构建它。
  • @HansPassant,不仅仅是nop 操作码(我可以很容易地忽略这些),IL 代码的流程实际上是不同的。我更新了我的问题以添加更多详细信息。

标签: c# optimization compiler-construction il


【解决方案1】:

如果您将用作动态方法模板的方法相对简单 - 并且不依赖于其他方法。然后只需将其放入它自己的程序集中并为该程序集打开优化。

就原始问题而言,因为 MSIL 是一种基于堆栈的语言。并且规范保证ret 语句中的堆栈状态,您可以 100% 确定您可以毫无问题地添加尾部前缀。然而,它也不太可能真正增加任何好处,因为我还没有真正看到 JIT 使用尾部前缀来实际优化最终 jitted 的代码。

【讨论】:

【解决方案2】:

您是否可以使用 Microsoft.CSharp.CSharpCodeProvider 动态生成原始方法代码?

如果您控制方法编译,则可以在使用CompilerOptions调用编译器时设置选项。

【讨论】:

  • 那无济于事;我不想动态生成代码(不是我的意思是原始代码)
【解决方案3】:

只要你使用 C#,你就永远无法确定你得到了尾调用优化。

特别是即使使用call ... ret,JITter 也不能保证尾调用。因此,依赖尾调用优化(以避免堆栈溢出)的 IMO C# 代码被简单地破坏了。在 C# 中尾调用优化纯粹是一种性能优化。

使用能够可靠发出尾调用的语言,或重写您的方法,使其不需要尾调用。

【讨论】:

  • 我知道 C# 不适合尾调用,我只是在做概念验证...别担心,我不打算在生产代码中使用它;)跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-26
  • 1970-01-01
  • 1970-01-01
  • 2014-05-01
  • 2016-07-21
  • 2017-11-05
  • 1970-01-01
相关资源
最近更新 更多