作者:波多爾斯基
連結: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.CompilerServices.MethodImpl屬性和MethodImplOptions中指定的選項。NoOptimization選項可以關閉最佳化,NoInlining阻止方法被行內,AggressiveInlining (.NET 4.5)選項推薦(不僅僅是提示)即時編譯器將一個方法行內。
結語
話說整點這個東西有點什麼用呢?
要說是有助於更好理解.NET的執行機制會不會有人打我…
說點實際的,有的童鞋在寫延時程式時,timer.Interval = 10 * 60 * 1000,作為強迫症患者,生怕這麼寫不好,影響程式執行。但是,這種寫法完全不會對程式的執行有任何影響,我認為還應該推薦,因為增加了程式的可讀性,上面的程式碼段就是簡單的10分鐘,一看就明白,要是算出來反而可讀性差。另外,分支簡化也有助於我們專心依照業務邏輯去編寫程式碼,而不需要過多考慮程式碼的分支問題。其他的用途各位看官自行發揮啦。
朋友會在“發現-看一看”看到你“在看”的內容