C#编译器优化那点事
使用C#編寫程序,給最終用戶的程序,是需要使用release配置的,而release配置和debug配置,有一個關鍵區(qū)別,就是release的編譯器優(yōu)化默認是啟用的。
優(yōu)化代碼開關即optimize開關,和debug開關一起,有以下幾種組合。
在Visual Sutdio中新建一個C#項目時,
項目的“調(diào)試”(Debug)配置的是/optimize-和/debug:full開關,
而“發(fā)布”(Release)配置指定的是/optimize+和/debug:pdbonly開關
optimize-/+決定了編譯器是否優(yōu)化代碼,optimize-就是不優(yōu)化了,但是通常,有一些基本的“優(yōu)化”工作,無論是否指定optimize+,都會執(zhí)行。
optimize- and optimize+
該項功能主要用于動態(tài)語義分析,幫助我們更好地編寫代碼。
常量計算
在寫程序的時候,有時能看見代碼下面劃了一道紅波浪線,那就是編譯器動態(tài)檢查。常量計算,就是這樣,編譯器會計算常量,幫助判斷其他錯誤。
簡單分支檢查
如果swtich寫了兩個以上的相同條件,或者分支明顯無法訪問到,都會彈出提示。
未使用變量
不多說明,直接看圖。
使用未賦值變量
不多說,看圖。
局限
使用變量參與計算,隨便寫一個算式,就可以繞過一些檢查,雖然我們看來是明顯有問題的。
optimize+ only
首先需要了解c#代碼編譯的過程,如下圖:
圖片來自http://www.cnblogs.com/rush/p/3155665.html
C# compiler將C#代碼生成IL代碼的就是所謂的編譯器優(yōu)化。先說重點。
.NET的JIT機制,主要優(yōu)化在JIT中完成,編譯器optimize只做一點簡單的工作。(劃重點)
探究一下到底干了點啥吧,以下是使用到的工具。
Tools:
Visual studio 2017 community targeting .net core 2.0
IL DASM(vs自帶)
使用IL DASM可以查看編譯器生成的IL代碼,這樣就能看到優(yōu)化的作用了。IL代碼的用途與機制不是本文的重點,不明白的同學可以先去看看《C# via CLR》(好書推薦)。
按照優(yōu)化的類型進行了簡單的分類。
從未使用變量
代碼如下:
using System.Threading.Tasks;
namespace CompileOpt{ ?
?class Program{ ? ? ?
??static void Main(string[] args) ? ? ? ?{ ? ? ? ?
??? ?int x = 3;Console.WriteLine("sg");}} }
未優(yōu)化的時候
.method private hidebysigstatic 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
使用優(yōu)化開關優(yōu)化之后:
.method private hidebysigstatic void ?Main(string[] args) cil managed{.entrypoint ?// 代碼大小 ? ? ? 11 (0xb).maxstack ?8IL_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)也消失了。
所以,整個沒有使用的變量,在設置為優(yōu)化的時候,就直接消失了,就像從來沒有寫過一樣。
空try catch語句
代碼如下:
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);}}} }
未優(yōu)化
.method private hidebysigstatic 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 .trycatch [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 .trycatch [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 .tryfinally{ ? ?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
優(yōu)化開關開啟:
.method private hidebysigstatic void ?Main(string[] args) cil managed{.entrypoint ?// 代碼大小 ? ? ? 19 (0x13).maxstack ?1.try{IL_0000: ?leave.s ? ?IL_0012} ?// end .tryfinally{IL_0002: ?call ? ? ? valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now()IL_0007: ?box ? ? ? ?[System.Runtime]System.DateTimeIL_000c: ?call ? ? ? void [System.Console]System.Console::WriteLine(object)IL_0011: ?endfinally} ?// end handlerIL_0012: ?ret } // end of method Program::Main
很明顯可以看到,空的try catch直接消失了,但是空的try catch finally代碼是不會消失的,但是也不會直接調(diào)用finally內(nèi)的代碼(即還是會生成try代碼段)。
分支簡化
代碼如下:
using System.Threading.Tasks;
namespace CompileOpt{ ?
? ?class Program{ ? ? ? ?static void Main(string[] args) ? ? ? ?{ ? ? ?
? ?? ? ?int x = 3; ? ? ? ? ?
? ?? ? ? ?if (x == 3) ? ? ? ? ? ? ?
? ?? ? ? ? ?goto LABEL1; ? ? ? ? ?
? ?? ? ? ?elsegoto LABEL2;LABEL2: return;LABEL1: return;}} }
未優(yōu)化的情況下:
.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優(yōu)化:
.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優(yōu)化的情況下,一些分支會被簡化,使得調(diào)用更加簡潔。
跳轉(zhuǎn)簡化
代碼如下:
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;} ? ? } }
未優(yōu)化:
.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優(yōu)化后:
.method private hidebysigstatic void ?Main(string[] args) cil managed{.entrypoint ?// 代碼大小 ? ? ? 21 (0x15).maxstack ?8IL_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
一些多層的標簽跳轉(zhuǎn)會得到簡化,優(yōu)化器就是人狠話不多。
臨時變量消除
一些臨時變量(中間變量)會被簡化消除。代碼如下:
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);}}} }
只顯示最關鍵的變量聲明部分,未優(yōu)化的代碼如下:
.method private hidebysigstatic 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
優(yōu)化后:
.method private hidebysigstatic 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字段,程序更加緊湊了。
編譯器版本不同,對應的優(yōu)化手段也不盡相同,以上只列出了一些,應該還有一些沒有講到的,歡迎補充。
延伸閱讀:.NET中的優(yōu)化(轉(zhuǎn)載自http://blog.jobbole.com/84712/)
在.NET的編譯模型中沒有鏈接器。但是有一個源代碼編譯器(C# compiler)和即時編譯器(JIT compiler),源代碼編譯器只進行很小的一部分優(yōu)化。比如它不會執(zhí)行函數(shù)內(nèi)聯(lián)和循環(huán)優(yōu)化。
從優(yōu)化能力上來講RyuJIT和Visual C++有什么不同呢?因為RyuJIT是在運行時完成其工作的,所以它可以完成一些Visual C++不能完成的工作。比如在運行時,RyuJIT可能會判定,在這次程序的運行中一個if語句的條件永遠不會為true,所以就可以將它移除。RyuJIT也可以利用他所運行的處理器的能力。比如如果處理器支持SSE4.1,即時編譯器就會只寫出sumOfCubes函數(shù)的SSE4.1指令,讓生成打的代碼更加緊湊。但是它不能花更多的時間來優(yōu)化代碼,因為即時編譯所花的時間會影響到程序的性能。
在當前控制托管代碼的能力是很有限的。C#和VB編譯器只允許使用/optimize編譯器開關打開或者關閉優(yōu)化功能。為了控制即時編譯優(yōu)化,你可以在方法上使用System.Runtime.Compiler-Services.MethodImpl屬性和MethodImplOptions中指定的選項。NoOptimization選項可以關閉優(yōu)化,NoInlining阻止方法被內(nèi)聯(lián),AggressiveInlining (.NET 4.5)選項推薦(不僅僅是提示)即時編譯器將一個方法內(nèi)聯(lián)。
結語
話說整點這個東西有點什么用呢?
要說是有助于更好理解.NET的運行機制會不會有人打我...
說點實際的,有的童鞋在寫延時程序時,timer.Interval = 10 * 60 * 1000,作為強迫癥患者,生怕這么寫不好,影響程序執(zhí)行。但是,這種寫法完全不會對程序的執(zhí)行有任何影響,我認為還應該推薦,因為增加了程序的可讀性,上面的代碼段就是簡單的10分鐘,一看就明白,要是算出來反而可讀性差。另外,分支簡化也有助于我們專心依照業(yè)務邏輯去編寫代碼,而不需要過多考慮代碼的分支問題。其他的用途各位看官自行發(fā)揮啦。
原文地址: http://www.cnblogs.com/podolski/p/8987595.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結
以上是生活随笔為你收集整理的C#编译器优化那点事的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Microsoft Build 2018
- 下一篇: 如何在Visual Studio 201