C#编译器优化

https://www.cnblogs.com/podolski/p/8987595.html

使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的。

优化代码开关即optimize开关,和debug开关一起,有以下几种组合。

在Visual Sutdio中新建一个C#项目时,项目的“调试”(Debug)配置的是/optimize-和/debug:full开关,而“发布”(Release)配置指定的是/optimize+和/debug:pdbonly开关

optimize-/+决定了编译器是否优化代码,optimize-就是不优化了,但是通常,有一些基本的“优化”工作,无论是否指定optimize+,都会执行。

optimize- and optimize+

该项功能主要用于动态语义分析,帮助我们更好地编写代码。

常量计算

在写程序的时候,有时能看见代码下面划了一道红波浪线,那就是编译器动态检查。常量计算,就是这样,编译器会计算常量,帮助判断其他错误。

简单分支检查

如果swtich写了两个以上的相同条件,或者分支明显无法访问到,都会弹出提示。

未使用变量

不多说明,直接看图。

使用未赋值变量

不多说,看图。

局限

使用变量参与计算,随便写一个算式,就可以绕过一些检查,虽然我们看来是明显有问题的。

optimize+ only

首先需要了解c#代码编译的过程,如下图:

图片来自http://www.cnblogs.com/rush/p/3155665.html

C# compiler将C#代码生成IL代码的就是所谓的编译器优化。先说重点。

.NET的JIT机制,主要优化在JIT中完成,编译器optimize只做一点简单的工作。(划重点)

探究一下到底干了点啥吧,以下是使用到的工具。

Tools:

Visual studio 2017 community targeting .net core 2.0

IL DASM(vs自带)

使用IL DASM可以查看编译器生成的IL代码,这样就能看到优化的作用了。IL代码的用途与机制不是本文的重点,不明白的同学可以先去看看《C# via CLR》(好书推荐)。

按照优化的类型进行了简单的分类。

从未使用变量

代码如下:

using System;

using System.Threading.Tasks;

namespace CompileOpt

{

class Program

{

static void Main(string[] args)

{

int x = 3;

Console.WriteLine("sg");

}

}

}

未优化的时候

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 15 (0xf)

.maxstack 1

.locals init (int32 V_0)

IL_0000: nop

IL_0001: ldc.i4.3

IL_0002: stloc.0

IL_0003: ldstr "sg"

IL_0008: call void [System.Console]System.Console::WriteLine(string)

IL_000d: nop

IL_000e: ret

} // end of method Program::Main

使用优化开关优化之后:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 11 (0xb)

.maxstack 8

IL_0000: ldstr "sg"

IL_0005: call void [System.Console]System.Console::WriteLine(string)

IL_000a: ret

} // end of method Program::Main

.locals init (int32 V_0)消失了(局部变量,类型为int32)

ldc.i4.3(将3推送到堆栈上)和stloc.0(将值从堆栈弹出到局部变量 0)也消失了。

所以,整个没有使用的变量,在设置为优化的时候,就直接消失了,就像从来没有写过一样。

空try catch语句

代码如下:

using System;

using System.Threading.Tasks;

namespace CompileOpt

{

class Program

{

static void Main(string[] args)

{

try

{

  1. }
  2. catch (Exception)
  3. {
  4. Console.WriteLine(DateTime.Now);
  5. }
  6. try
  7. {
  8. }
  9. catch (Exception)
  10. {
  11. Console.WriteLine(DateTime.Now);
  12. }
  13. finally
  14. {
  15. Console.WriteLine(DateTime.Now);
  16. }
  17. }
  18. }

}

未优化

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 74 (0x4a)

.maxstack 1

IL_0000: nop

.try

{

IL_0001: nop

IL_0002: nop

IL_0003: leave.s IL_001a

} // end .try

catch [System.Runtime]System.Exception

{

IL_0005: pop

IL_0006: nop

IL_0007: call valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()

IL_000c: box [System.Runtime]System.DateTime

IL_0011: call void [System.Console]System.Console::WriteLine(object)

IL_0016: nop

IL_0017: nop

IL_0018: leave.s IL_001a

} // end handler

IL_001a: nop

.try

{

.try

{

IL_001b: nop

IL_001c: nop

IL_001d: leave.s IL_0034

} // end .try

catch [System.Runtime]System.Exception

{

IL_001f: pop

IL_0020: nop

IL_0021: call valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()

IL_0026: box [System.Runtime]System.DateTime

IL_002b: call void [System.Console]System.Console::WriteLine(object)

IL_0030: nop

IL_0031: nop

IL_0032: leave.s IL_0034

} // end handler

IL_0034: leave.s IL_0049

} // end .try

finally

{

IL_0036: nop

IL_0037: call valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()

IL_003c: box [System.Runtime]System.DateTime

IL_0041: call void [System.Console]System.Console::WriteLine(object)

IL_0046: nop

IL_0047: nop

IL_0048: endfinally

} // end handler

IL_0049: ret

} // end of method Program::Main

优化开关开启:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 19 (0x13)

.maxstack 1

.try

{

IL_0000: leave.s IL_0012

} // end .try

finally

{

IL_0002: call valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()

IL_0007: box [System.Runtime]System.DateTime

IL_000c: call void [System.Console]System.Console::WriteLine(object)

IL_0011: endfinally

} // end handler

IL_0012: ret

} // end of method Program::Main

很明显可以看到,空的try catch直接消失了,但是空的try catch finally代码是不会消失的,但是也不会直接调用finally内的代码(即还是会生成try代码段)。

分支简化

代码如下:

using System;

using System.Threading.Tasks;

namespace CompileOpt

{

class Program

{

static void Main(string[] args)

{

int x = 3;

if (x == 3)

goto LABEL1;

else

goto LABEL2;

LABEL2: return;

LABEL1: return;

}

}

}

未优化的情况下:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 22 (0x16)

.maxstack 2

.locals init (int32 V_0,

bool V_1)

IL_0000: nop

IL_0001: ldc.i4.3

IL_0002: stloc.0

IL_0003: ldloc.0

IL_0004: ldc.i4.3

IL_0005: ceq

IL_0007: stloc.1

IL_0008: ldloc.1

IL_0009: brfalse.s IL_000d

IL_000b: br.s IL_0012

IL_000d: br.s IL_000f

IL_000f: nop

IL_0010: br.s IL_0015

IL_0012: nop

IL_0013: br.s IL_0015

IL_0015: ret

} // end of method Program::Main

优化:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 5 (0x5)

.maxstack 8

IL_0000: ldc.i4.3

IL_0001: ldc.i4.3

IL_0002: pop

IL_0003: pop

IL_0004: ret

} // end of method Program::Main

优化的情况下,一些分支会被简化,使得调用更加简洁。

跳转简化

代码如下:

using System;

using System.Threading.Tasks;

namespace CompileOpt

{

class Program

{

static void Main(string[] args)

{

goto LABEL1;

LABEL2: Console.WriteLine("234");

Console.WriteLine("123");

return;

LABEL1: goto LABEL2;

}

}

}

未优化:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 32 (0x20)

.maxstack 8

IL_0000: nop

IL_0001: br.s IL_001c

IL_0003: nop

IL_0004: ldstr "234"

IL_0009: call void [System.Console]System.Console::WriteLine(string)

IL_000e: nop

IL_000f: ldstr "123"

IL_0014: call void [System.Console]System.Console::WriteLine(string)

IL_0019: nop

IL_001a: br.s IL_001f

IL_001c: nop

IL_001d: br.s IL_0003

IL_001f: ret

} // end of method Program::Main

优化后:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 21 (0x15)

.maxstack 8

IL_0000: ldstr "234"

IL_0005: call void [System.Console]System.Console::WriteLine(string)

IL_000a: ldstr "123"

IL_000f: call void [System.Console]System.Console::WriteLine(string)

IL_0014: ret

} // end of method Program::Main

一些多层的标签跳转会得到简化,优化器就是人狠话不多。

临时变量消除

一些临时变量(中间变量)会被简化消除。代码如下:

using System;

using System.Threading.Tasks;

namespace CompileOpt

{

class Program

{

static void Main(string[] args)

{

for (int i = 0; i < 3; i++)

{

Console.WriteLine(i);

}

for (int i = 0; i < 3; i++)

{

Console.WriteLine(i + 1);

}

}

}

}

只显示最关键的变量声明部分,未优化的代码如下:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 54 (0x36)

.maxstack 2

.locals init (int32 V_0,

bool V_1,

int32 V_2,

bool V_3)

IL_0000: nop

优化后:

.method private hidebysig static void Main(string[] args) cil managed

{

.entrypoint

// 代码大小 39 (0x27)

.maxstack 2

.locals init (int32 V_0,

int32 V_1)

IL_0000: ldc.i4.0

很显然,中间的bool型比较变量消失了。

空指令删除

看第一个例子,很明显,代码中没有了nop字段,程序更加紧凑了。

编译器版本不同,对应的优化手段也不尽相同,以上只列出了一些,应该还有一些没有讲到的,欢迎补充。

延伸阅读:.NET中的优化(转载自http://blog.jobbole.com/84712/)

在.NET的编译模型中没有链接器。但是有一个源代码编译器(C# compiler)和即时编译器(JIT compiler),源代码编译器只进行很小的一部分优化。比如它不会执行函数内联和循环优化。

从优化能力上来讲RyuJIT和Visual C++有什么不同呢?因为RyuJIT是在运行时完成其工作的,所以它可以完成一些Visual C++不能完成的工作。比如在运行时,RyuJIT可能会判定,在这次程序的运行中一个if语句的条件永远不会为true,所以就可以将它移除。RyuJIT也可以利用他所运行的处理器的能力。比如如果处理器支持SSE4.1,即时编译器就会只写出sumOfCubes函数的SSE4.1指令,让生成打的代码更加紧凑。但是它不能花更多的时间来优化代码,因为即时编译所花的时间会影响到程序的性能。

在当前控制托管代码的能力是很有限的。C#和VB编译器只允许使用/optimize编译器开关打开或者关闭优化功能。为了控制即时编译优化,你可以在方法上使用System.Runtime.Compiler­Services.MethodImpl属性和MethodImplOptions中指定的选项。NoOptimization选项可以关闭优化,NoInlining阻止方法被内联,AggressiveInlining (.NET 4.5)选项推荐(不仅仅是提示)即时编译器将一个方法内联。

结语

话说整点这个东西有点什么用呢?

要说是有助于更好理解.NET的运行机制会不会有人打我...

说点实际的,有的童鞋在写延时程序时,timer.Interval = 10 * 60 * 1000,作为强迫症患者,生怕这么写不好,影响程序执行。但是,这种写法完全不会对程序的执行有任何影响,我认为还应该推荐,因为增加了程序的可读性,上面的代码段就是简单的10分钟,一看就明白,要是算出来反而可读性差。另外,分支简化也有助于我们专心依照业务逻辑去编写代码,而不需要过多考虑代码的分支问题。其他的用途各位看官自行发挥啦。

C#编译器优化的更多相关文章

  1. 探索c#之尾递归编译器优化

    阅读目录: 递归运用 尾递归优化 编译器优化 递归运用 一个函数直接或间接的调用自身,这个函数即可叫做递归函数. 递归主要功能是把问题转换成较小规模的子问题,以子问题的解去逐渐逼近最终结果. 递归最重 ...

  2. VS编译器优化诱发一个的Bug

    VS编译器优化诱发一个的Bug Bug的背景 我正在把某个C++下的驱动程序移植到C下,前几天发生了一个比较诡异的问题. 驱动程序有一个bug,但是这个bug只能 Win32 Release 版本下的 ...

  3. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part6:Move语义和编译器优化

    本文为第六部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.ht ...

  4. Visual C++中的编译器优化

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:Visual C++中的编译器优化.

  5. gcc编译器优化给我们带来的麻烦???

    gcc编译器优化给我们带来的麻烦??? 今天看到一个很有趣的程序,如下: ? 1 2 3 4 5 6 7 8 9 int main() {     const int a = 1;     int * ...

  6. C#编译器优化那点事

    使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的. 优化代码开关即optimize开 ...

  7. 【转】C 编译器优化过程中的 Bug

    C 编译器优化过程中的 Bug 一个朋友向我指出一个最近他们发现的 GCC 编译器优化过程(加上 -O3 选项)里的 bug,导致他们的产品出现非常诡异的行为.这使我想起以前见过的一个 GCC bug ...

  8. C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理

    C#编译器优化那点事   使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...

  9. 2018-8-10-win10-uwp-禁止编译器优化代码

    title author date CreateTime categories win10 uwp 禁止编译器优化代码 lindexi 2018-08-10 19:16:50 +0800 2018-2 ...

随机推荐

  1. android黑科技系列——Android中新型安全防护策略

    一.前言 最近有一个同学,发给我一个设备流量访问检测工具,但是奇怪的是,他从GP上下载下来之后安装就没有数据了,而在GP上直接安装就可以.二次打包也会有问题.所以这里就可以判断这个app应该是有签名校 ...

  2. ie8及其以下版本兼容性问题之圆角

    解决办法:在http://css3pie.com/页面下载一个PIE.htc的文件,加载到根目录下,然后在css中加上一句behavior:url(../js/PIE.htc);如下: .border ...

  3. HDU_1969_二分

    Pie Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submiss ...

  4. Cache-Control官方文档

    https://tools.ietf.org/html/draft-ietf-httpbis-p6-cache-25#page-21 5.2. Cache-Control The "Cach ...

  5. H5-data属性的一个问题

    关于前端存数据的问题,前面写过一个博客:前端页面存取数据 看个例子: <!DOCTYPE html> <html lang="en"> <head&g ...

  6. js-构造数组

    js中,字符串的特性跟数组非常类似.数组是一种很重要的数据结构.在java中,数组声明的时候就要为其指定类型,数组中只能放同一种类型的数据.Js中的数组可以放不同的类型,但是是有序的,类似于java中 ...

  7. PAT_A1098#Insertion or Heap Sort

    Source: PAT_A1098 Insertion or Heap Sort (25 分) Description: According to Wikipedia: Insertion sort  ...

  8. Python-jquery-datatable服务器分页.

    第一步: js初始化: part01:ajax设置 part02:语言设置 part03: 行列设置: part04: 具体渲染设置: 第二部: 服务端设置: 第一部分 获取固定表示: 第二部分 对数 ...

  9. PAT 1075. PAT Judge

    The ranklist of PAT is generated from the status list, which shows the scores of the submittions. Th ...

  10. JAVA关键技术

    通用技术方面 MVC 1)概念 MVC是一个架构模式,它分离了表现与交互.它被分为三个核心部件:模型-model.视图-view.控制器-controller 2)工作原理 所有的终端用户请求被发送到 ...