编译器何时优化我的代码?

9
我正在尝试构建一个代码示例,展示编译器在乘以2的幂次方数时进行代码优化的过程。但是当我打开代码优化选项后,IL(中间语言)基本上保持不变。你有什么想法吗?
代码如下:
int nr;
int result;
var stopwatch = new Stopwatch();

nr = 5;

stopwatch.Start();
    result = nr * 4;
stopwatch.Stop();

Console.WriteLine(result);
Console.WriteLine(stopwatch.Elapsed.ToString() + "ms ellapsed");
stopwatch.Reset();

stopwatch.Start();
result = nr << 2;
stopwatch.Stop();

Console.WriteLine(result);
Console.WriteLine(stopwatch.Elapsed.ToString() + "ms ellapsed");

非优化的中间语言代码:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       130 (0x82)
  .maxstack  2
  .locals init ([0] int32 nr,
           [1] int32 result,
           [2] class [System]System.Diagnostics.Stopwatch stopwatch,
           [3] valuetype [mscorlib]System.TimeSpan CS$0$0000,
           [4] valuetype [mscorlib]System.TimeSpan CS$0$0001)
  IL_0000:  newobj     instance void [System]System.Diagnostics.Stopwatch::.ctor()
  IL_0005:  stloc.2
  IL_0006:  ldc.i4.5
  IL_0007:  stloc.0
  IL_0008:  ldloc.2
  IL_0009:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Start()
  IL_000e:  ldloc.0
  IL_000f:  ldc.i4.4
  IL_0010:  mul
  IL_0011:  stloc.1
  IL_0012:  ldloc.2
  IL_0013:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Stop()
  IL_0018:  ldloc.1
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_001e:  ldloc.2
  IL_001f:  callvirt   instance valuetype [mscorlib]System.TimeSpan [System]System.Diagnostics.Stopwatch::get_Elapsed()
  IL_0024:  stloc.3
  IL_0025:  ldloca.s   CS$0$0000
  IL_0027:  constrained. [mscorlib]System.TimeSpan
  IL_002d:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0032:  ldstr      "ms ellapsed"
  IL_0037:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_003c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0041:  ldloc.2
  IL_0042:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Reset()
  IL_0047:  ldloc.2
  IL_0048:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Start()
  IL_004d:  ldloc.0
  IL_004e:  ldc.i4.2
  IL_004f:  shl
  IL_0050:  stloc.1
  IL_0051:  ldloc.2
  IL_0052:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Stop()
  IL_0057:  ldloc.1
  IL_0058:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_005d:  ldloc.2
  IL_005e:  callvirt   instance valuetype [mscorlib]System.TimeSpan [System]System.Diagnostics.Stopwatch::get_Elapsed()
  IL_0063:  stloc.s    CS$0$0001
  IL_0065:  ldloca.s   CS$0$0001
  IL_0067:  constrained. [mscorlib]System.TimeSpan
  IL_006d:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0072:  ldstr      "ms ellapsed"
  IL_0077:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_007c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0081:  ret
} // end of method Program::Main

优化过的中间语言(IL):

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       130 (0x82)
  .maxstack  2
  .locals init ([0] int32 nr,
           [1] int32 result,
           [2] class [System]System.Diagnostics.Stopwatch stopwatch,
           [3] valuetype [mscorlib]System.TimeSpan CS$0$0000,
           [4] valuetype [mscorlib]System.TimeSpan CS$0$0001)
  IL_0000:  newobj     instance void [System]System.Diagnostics.Stopwatch::.ctor()
  IL_0005:  stloc.2
  IL_0006:  ldc.i4.5
  IL_0007:  stloc.0
  IL_0008:  ldloc.2
  IL_0009:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Start()
  IL_000e:  ldloc.0
  IL_000f:  ldc.i4.4
  IL_0010:  mul
  IL_0011:  stloc.1
  IL_0012:  ldloc.2
  IL_0013:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Stop()
  IL_0018:  ldloc.1
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_001e:  ldloc.2
  IL_001f:  callvirt   instance valuetype [mscorlib]System.TimeSpan [System]System.Diagnostics.Stopwatch::get_Elapsed()
  IL_0024:  stloc.3
  IL_0025:  ldloca.s   CS$0$0000
  IL_0027:  constrained. [mscorlib]System.TimeSpan
  IL_002d:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0032:  ldstr      "ms ellapsed"
  IL_0037:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_003c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0041:  ldloc.2
  IL_0042:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Reset()
  IL_0047:  ldloc.2
  IL_0048:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Start()
  IL_004d:  ldloc.0
  IL_004e:  ldc.i4.2
  IL_004f:  shl
  IL_0050:  stloc.1
  IL_0051:  ldloc.2
  IL_0052:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Stop()
  IL_0057:  ldloc.1
  IL_0058:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_005d:  ldloc.2
  IL_005e:  callvirt   instance valuetype [mscorlib]System.TimeSpan [System]System.Diagnostics.Stopwatch::get_Elapsed()
  IL_0063:  stloc.s    CS$0$0001
  IL_0065:  ldloca.s   CS$0$0001
  IL_0067:  constrained. [mscorlib]System.TimeSpan
  IL_006d:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0072:  ldstr      "ms ellapsed"
  IL_0077:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_007c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0081:  ret
} // end of method Program::Main

我以为编译器会将乘法语句优化为移位语句?我的IL知识非常有限(甚至可以说是没有)。

2
如果 mul 被优化为 shl -- 我不知道是否会这样做 -- 它几乎肯定是在 IL 转换为特定平台代码时完成的。 - LukeH
2个回答

7

这是Release版本中Jitter生成的代码:

0000003e  mov         ecx,14h 

优化器非常聪明,当它知道操作数的值时,不会生成乘法代码。如果您将nr = 5; 替换为nr = int.Parse(“5”),这样JIT无法知道操作数的值,则它将为乘法生成以下代码:

0000005c  lea         ebx,[rdi*4+00000000h] 

这利用了CPU地址生成逻辑中内置的乘法器,允许指令与使用ALU的另一个指令重叠。这使得乘法本质上是免费的。这是64位Jitter的输出,32位Jitter生成如下:

0000004d  shl         edi,2 

这正是你所期望的。我在这篇文章中记录了即时编译器执行的优化类型。


7
"optimize" 标志在 C# 到 IL 编译阶段并没有太大作用。它确实会有所影响,但对于这种情况却不起作用。
我希望这种优化由 JIT 编译器处理。

有没有关于CSC优化的具体内容的文档或资料? - Zenwalker
@zenwalker:据我所知没有这样的功能 - 这是编译器团队应该在实现细节上在版本之间进行更改的事情。你可能会发现一些关于它的博客文章... - Jon Skeet
5
@zenwalker:http://blogs.msdn.com/b/ericlippert/archive/2009/06/11/what-does-the-optimize-switch-do.aspx - LukeH
谢谢你的回答。我原以为这是一个基本的优化例程,用移位运算符替代乘法运算。我不认为JIT编译器会替换它,因为乘法语句比移位语句慢6倍。 - Luc Bos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接