我可以强制编译器优化特定的方法吗

本文关键字:方法 优化 编译器 我可以 | 更新日期: 2023-09-27 18:27:32

是否有一个属性可以用来告诉编译器,即使没有设置全局/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什么都没有。但对于未优化的版本,我不太确定。。。递归调用的结果存储在一个局部变量中,然后有一个无用的分支跳到下一条指令,加载并返回局部变量。所以我没有简单的方法来检查递归调用是否真的是最后一条指令,所以我不能确定尾调用优化是否可以应用。

我可以强制编译器优化特定的方法吗

如果要用作动态方法模板的方法相对简单,并且不依赖于其他方法。然后把它放在它自己的程序集中,并对该程序集进行优化。

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

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

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

只要使用C#,就永远无法确保获得尾调用优化。

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

使用可靠地发出尾部调用的语言,或者重写方法,使其不需要尾部调用。