.NET是否在编译之前优化代码?

时间:2015-09-23 13:50:00

标签: c# compiler-construction jit

我正在浏览我的代码并回忆起ActionScript编译器所做的事情:它简化了不必要/冗余的代码,然后编译结果。我想知道C#是否有相同的程序。

如果我理解正确,并假设这是有效的ActionScript(我知道不是),编译器会采取这个:

byte[] result = ServerWC.UploadValues("http://localhost", srvNvc);
Console.WriteLine(Encoding.ASCII.GetString(result));

并将其简化为:

Console.WriteLine(Encoding.ASCII.GetString(ServerWC.UploadValues("http://localhost", srvNvc)));

编译前。哪个编译器,C# - > IL或IL->机器会进行优化?所有编译器都以这种方式工作吗?

2 个答案:

答案 0 :(得分:1)

您发布的代码没有必要。您的更改根本没有进行任何简化 - 您只将伪命令性代码块更改为单个表达式。

.NET编译过程有点复杂 - 首先,您拥有安全的托管C#。然后,您拥有安全,可管理的IL。最后,您将获得不安全的本机x86程序集(例如)。

IL基本上是基于堆栈的。所以你的代码会变成这样的东西:

call UploadValues
call Encoding::ASCII
call Encoding.GetString
call Console::WriteLine

(不用说,这是严重过度简化 - 但它是一种相当高级的“类似汇编”的语言)

你可以看到即使在这里,也不再有任何局部变量。它只是虚拟堆栈上的一个值。当然,不同的编译器有权以不同的方式实现这一点。例如,你可以得到这样的东西:

call UploadValues
stloc.0
ldloc.0
call Encoding::ASCII
call Encoding.GetString
call Console::WriteLine

但显而易见的是,这确实是一个额外的步骤,而不是缺少优化:)

新的Roslyn编译器确实利用了旧的编译器没有的一些东西。例如,如果在同一方法中多次使用相同的局部,那么它也可能避开局部 - 一个很好的例子就是今天的问题VS2015 stloc.0 and ldloc.0 are removed from compilation instructions

但是,此处发生的任何事情都不一定会影响生成的代码的性能。下一步,从IL到x86的JIT编译是执行大多数优化的部分。这包括决定IL中的本地和虚拟堆栈将被表示为例如寄存器中的值。

至于ActionScript,我认为同样适用于AS3。旧的解释版本可能存在差异,但当它跳转到一个完整的JITted虚拟机时,这种差异可能会消失。

答案 1 :(得分:1)

为什么不试试?在Reflector中我可以看到这里没有执行这样的优化:

private void m1()
{
    byte[] result = ServerWC.UploadValues("http://localhost", this.srvNvc);
    Console.WriteLine(Encoding.ASCII.GetString(result));
}

private void m2()
{
    Console.WriteLine(Encoding.ASCII.GetString(ServerWC.UploadValues("http://localhost", this.srvNvc)));
}

发布版本的IL代码:

.method private hidebysig instance void m1() cil managed
{
    .maxstack 2
    .locals init (
        [0] uint8[] result)
    L_0000: ldstr "http://localhost"
    L_0005: ldarg.0 
    L_0006: ldfld object Test.Program::srvNvc
    L_000b: call uint8[] Test.ServerWC::UploadValues(string, object)
    L_0010: stloc.0 
    L_0011: call class [mscorlib]System.Text.Encoding [mscorlib]System.Text.Encoding::get_ASCII()
    L_0016: ldloc.0 
    L_0017: callvirt instance string [mscorlib]System.Text.Encoding::GetString(uint8[])
    L_001c: call void [mscorlib]System.Console::WriteLine(string)
    L_0021: ret 
}

.method private hidebysig instance void m2() cil managed
{
    .maxstack 8
    L_0000: call class [mscorlib]System.Text.Encoding [mscorlib]System.Text.Encoding::get_ASCII()
    L_0005: ldstr "http://localhost"
    L_000a: ldarg.0 
    L_000b: ldfld object Test.Program::srvNvc
    L_0010: call uint8[] Test.ServerWC::UploadValues(string, object)
    L_0015: callvirt instance string [mscorlib]System.Text.Encoding::GetString(uint8[])
    L_001a: call void [mscorlib]System.Console::WriteLine(string)
    L_001f: ret 
}

但编译器可以进行优化。例如,未编译未到达的语句(在返回或抛出异常之后)。同样,JIT也进行了优化。例如,如果您有泛型类型或方法,并将泛型值与null进行比较,那么如果实际构造的类型是值类型,则这部分代码将不会被JIT。

你的例子虽然不同。 C#是一种命令式语言,在这里你明确告诉必须创建一个局部变量。您甚至可以调试变量(也在发布模式下),这意味着它已经创建。在构造中,可能在不使用局部变量的情况下调用方法,但是指示编译器创建一个。 (虽然编译器优化了未使用的变量)。