使用C#編寫程序,給最終用戶的程序,是須要使用release配置的,而release配置和debug配置,有一個關鍵區別,就是release的編譯器優化默認是啓用的。
優化代碼開關即optimize開關,和debug開關一塊兒,有如下幾種組合。
html
在Visual Sutdio中新建一個C#項目時,
項目的「調試」(Debug)配置的是/optimize-和/debug:full開關,
而「發佈」(Release)配置指定的是/optimize+和/debug:pdbonly開關c#
optimize-/+決定了編譯器是否優化代碼,optimize-就是不優化了,可是一般,有一些基本的「優化」工做,不管是否指定optimize+,都會執行。ide
該項功能主要用於動態語義分析,幫助咱們更好地編寫代碼。函數
在寫程序的時候,有時能看見代碼下面劃了一道紅波浪線,那就是編譯器動態檢查。常量計算,就是這樣,編譯器會計算常量,幫助判斷其餘錯誤。
工具
若是swtich寫了兩個以上的相同條件,或者分支明顯沒法訪問到,都會彈出提示。
性能
很少說明,直接看圖。
優化
很少說,看圖。
spa
使用變量參與計算,隨便寫一個算式,就能夠繞過一些檢查,雖然咱們看來是明顯有問題的。
.net
首先須要瞭解c#代碼編譯的過程,以下圖:
圖片來自http://www.cnblogs.com/rush/p/3155665.htmldebug
C# compiler將C#代碼生成IL代碼的就是所謂的編譯器優化。先說重點。
.NET的JIT機制,主要優化在JIT中完成,編譯器optimize只作一點簡單的工做。(劃重點)
探究一下到底幹了點啥吧,如下是使用到的工具。
Tools:
Visual studio 2017 community targeting .net core 2.0
IL DASM(vs自帶)
按照優化的類型進行了簡單的分類。
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)也消失了。
因此,整個沒有使用的變量,在設置爲優化的時候,就直接消失了,就像歷來沒有寫過同樣。
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型比較變量消失了。
編譯器版本不一樣,對應的優化手段也不盡相同,以上只列出了一些,應該還有一些沒有講到的,歡迎補充。
在.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.CompilerServices.MethodImpl屬性和MethodImplOptions中指定的選項。NoOptimization選項能夠關閉優化,NoInlining阻止方法被內聯,AggressiveInlining (.NET 4.5)選項推薦(不只僅是提示)即時編譯器將一個方法內聯。
話說整點這個東西有點什麼用呢?
要說是有助於更好理解.NET的運行機制會不會有人打我...
說點實際的,有的童鞋在寫延時程序時,timer.Interval = 10 * 60 * 1000
,做爲強迫症患者,生怕這麼寫很差,影響程序執行。可是,這種寫法徹底不會對程序的執行有任何影響,我認爲還應該推薦,由於增長了程序的可讀性,上面的代碼段就是簡單的10分鐘,一看就明白,要是算出來反而可讀性差。另外,分支簡化也有助於咱們專心依照業務邏輯去編寫代碼,而不須要過多考慮代碼的分支問題。其餘的用途各位看官自行發揮啦。