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

{

        }
catch (Exception)
{
Console.WriteLine(DateTime.Now);
} try
{ }
catch (Exception)
{
Console.WriteLine(DateTime.Now); }
finally
{
Console.WriteLine(DateTime.Now); }
}
}

}

未优化

.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. IVVI SK3-02小骨酷派SK3-02 进入第三方 recovery 刷机 ROOT

    首先下载好工具:http://url.cn/5AS7IiB 备用连接 :https://pan.baidu.com/s/1jJmbYAi 本篇教程教你如何傻瓜式解锁BootLoader并进入临时rec ...

  2. 三维重建面试4:Jacobian矩阵和Hessian矩阵

    在使用BA平差之前,对每一个观测方程,得到一个代价函数.对多个路标,会产生一个多个代价函数的和的形式,对这个和进行最小二乘法进行求解,使用优化方法.相当于同时对相机位姿和路标进行调整,这就是所谓的BA ...

  3. Skyline Web 端数据浏览性能优化

    三维数据的效率一直是个瓶颈,特别是在Web端浏览一直是个问题,在IE内存限制1G的条件下,对于三维数据动不动几十G的数据量,这1G显得多么微不足道.虽然现在三维平台都是分级加载,或者在程序中采用数据分 ...

  4. [luogu1640 SCOI2010]连续攻击游戏 (二分图最大匹配)

    传送门 Description lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示.当他使用某种装备时,他只能使用该装备的某 ...

  5. [POI2012]OKR-A Horrible Poem

    正解:对于一个区间l,r,它的循环节长度一定是它的因数. 然后如果循环节是这个长度,那么[l+len,r]一定等于[l,r-len]. 然后每次询问的时候就把它的长度的最小质因子提出来. BZOJ上都 ...

  6. IDEA全局查找快捷键

    双击shift

  7. 42.query string分词

    主要知识点: 1.queery string 分词 2.38节中搜索结果解析 3,测试分词器     一.query string分词 query string必须以和index建立时相同的analy ...

  8. 将 Vue 组件库发布到 npm

    制作了一套自己的组件库,并发布到npm上,项目代码见 GitHub . 前期准备 有一个npm账号 安装了vue-cli 搭建项目 vue init webpack hg-vcomponents cd ...

  9. 数据持久层(DAO)通用API的实现

    在Web开发中,一般都分3层.Controller/Action 控制层,Service/Business 服务层/业务逻辑层,Dao 数据访问层/数据持久层. 在学习和工作的实践过程中,我发现很多功 ...

  10. (15)Spring Boot使用Druid和监控配置【从零开始学Spring Boot】

    Spring Boot 系列博客] 更多查看博客:http://412887952-qq-com.iteye.com/blog Spring Boot默认的数据源是:org.apache.tomcat ...