C#编译器本身不会在Release版本中对发出的IL进行很大的更改。值得注意的是,它不再发出允许您在花括号上设置断点的NOP操作码。最大的一个是内置在JIT编译器中的优化器。我知道它可以进行以下优化:
方法内联。方法调用由注入方法的代码代替。这是一个很大的问题,它使属性访问器基本上免费。
CPU寄存器分配。局部变量和方法参数可以保持存储在CPU寄存器中,而无需(或不经常)存储回堆栈帧。这是一个很大的问题,值得注意的是使调试优化的代码变得如此困难。并为volatile关键字赋予含义。
消除数组索引检查。使用数组时的一项重要优化(所有.NET集合类在内部使用数组)。当JIT编译器可以验证循环永远不会索引数组超出范围时,它将消除索引检查。大的一个。
循环展开。通过在主体中重复执行多达4次且减少循环的代码,可以改善具有小主体的循环。降低分支成本并改善处理器的超标量执行选项。
消除无效代码。像if(false){/ ... /} 这样的语句被完全消除。这可能是由于不断折叠和内嵌而发生的。在其他情况下,JIT编译器可以确定代码没有可能的副作用。这种优化使分析代码如此棘手。
代码提升。不受循环影响的循环内的代码可以移出循环。C编译器的优化器将花费更多的时间来寻找提升的机会。但是,由于需要进行数据流分析,因此这是一项昂贵的优化,抖动无法承受时间,因此只能举起明显的情况。迫使.NET程序员编写更好的源代码并自行提升。
常见子表达式消除。x = y + 4; z = y + 4; 变成z = x; 在dest [ix + 1] = src [ix + 1]等语句中很常见;为提高可读性而编写,而没有引入辅助变量。无需牺牲可读性。
不断折叠。x = 1 + 2; 变成x = 3; 这个简单的示例早已被编译器捕获,但发生在JIT时,其他优化使之成为可能。
复制传播。x = a; y = x; 变成y = a; 这有助于寄存器分配器做出更好的决策。x86抖动非常重要,因为它很少有寄存器可以使用。选择正确的选择对性能至关重要。
这些非常重要的优化可以带来很大的不同,例如,当您分析应用程序的Debug版本并将其与Release版本进行比较时。这只是真正重要,尽管当代码位于关键路径上时,所编写的代码的5%至10%实际上会影响程序的性能。JIT优化器不够聪明,无法预先知道什么是关键代码,它只能对所有代码应用“将其转到11”拨盘。
这些优化对程序执行时间的有效结果通常受在其他位置运行的代码的影响。读取文件,执行dbase查询等。使工作变得完全不可见。它不在乎:)
JIT优化器是非常可靠的代码,主要是因为它已经经受了数百万次测试。在程序的发布版本中出现问题的情况很少见。但是确实发生了。x64和x86抖动都存在结构问题。x86抖动在浮点一致性方面存在问题,当浮点计算的中间值以80位精度保存在FPU寄存器中,而不是在刷新到内存时会被截断时,会产生完全不同的结果。