我可以强制编译器优化特定的方法吗
本文关键字:方法 优化 编译器 我可以 | 更新日期: 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#中,尾调用优化纯粹是一种性能优化。
使用可靠地发出尾部调用的语言,或者重写方法,使其不需要尾部调用。