在應用程序開發過程中,開發者都力求寫出更加高效的代碼。但是當你想手工為C#編譯器優化代碼時,你的種種優化可能反倒會阻礙JIT進行更加高效的優化。因此,我們最好盡可能的寫出最清晰的代碼,將優化工作交給JIT編譯器去完成。
在.NET平台下開發程序的開發者都應該知道:.NET運行時將調用JIT編譯器來將C#編譯器生成的IL翻譯成機器碼。JIT不會在程序剛開始的時候就完全翻譯所有的IL代碼,CLR根據函數的粒度來逐一進行JIT編譯。沒有被調用的函數根本不會被JIT編譯,因此將那些非常重要的邏輯分解成更多的小方法要比把所有邏輯放在一起形成大型復雜函數更有效率。例如下面的代碼:
1 public string BuildMsg(bool takeFirstPath) 2 { 3 StringBuilder msg = new StringBuilder(); 4 5 if (takeFirstPath) 6 { 7 msg.Append("A problem occurred."); 8 msg.Append("\nThis is a problem."); 9 msg.Append("imagine much more text"); 10 } 11 else 12 { 13 msg.Append("This Path is not so bad."); 14 msg.Append("\nIt is only a minor inconvenience."); 15 msg.Append("Add more detailed diagnostics here."); 16 } 17 return msg; 18 }
在第一次調用BuildMsg時,if-else兩個分支都將被JIT編譯。而實際上僅需要編譯其中的一個分支就足夠了,我們可以拆分這個方法,對其進行優化,下面是優化后的代碼:

1 public string BuildMsg(bool takeFirstPath) 2 { 3 if (takeFirstPath) 4 { 5 return FirstPath(); 6 } 7 else 8 { 9 return SecondPath(); 10 } 11 } 12 13 public string FirstPath() 14 { 15 StringBuilder msg = new StringBuilder(); 16 17 msg.Append("A problem occurred."); 18 msg.Append("\nThis is a problem."); 19 msg.Append("imagine much more text"); 20 21 return msg.ToString(); 22 } 23 24 public string SecondPath() 25 { 26 StringBuilder msg = new StringBuilder(); 27 28 msg.Append("This Path is not so bad."); 29 msg.Append("\nIt is only a minor inconvenience."); 30 msg.Append("Add more detailed diagnostics here."); 31 32 return msg.ToString(); 33 } 34 }
這時候兩個方法可以根據需要再進行JIT編譯,而不必在第一次調用BuildMsg方法是進行。我們可以看出:更小的函數讓JIT編譯器更方便的根據需要進行編譯,而不是將時間浪費在不急於一時使用的代碼上。對於switch語句中的每個case中的代碼,這個規則的影響更明顯。
寄存器的優化
小而簡單的方法會讓JIT更容易的進行寄存器的選擇工作,即選擇哪個局部變量可以存放在寄存器中,而不是棧上。越少使用局部變量,也就讓JIT編譯器能夠更方便的找到最適合放在寄存器的那一些。而越小的函數包含的局部變量也越少,也就更方便JIT對寄存器進行優化。
內聯的優化
內聯表示把函數體替換到函數被調用的位置。由JIT編譯器負責決定哪些方法應該被內聯,當內聯可以有效提高效率時,JIT編譯器將自動執行。不過內聯的標准並不是固定的,且當前的規則也不能保證將來不會發生變化,此外,是否內聯完全由JIT自己決定。不過我們可以使用下面的特性選項通知JIT不要內聯某個方法:
1 [MethodImpl(MethodImplOptions.NoInlining)]
方法越簡單就越適合內聯。不過虛方法和包含ctry/catch代碼塊的方法將不會被內聯。內聯也改變了:代碼在執行時才會被JIT編譯 這一原則。所以在.NET平台下編程我們的責任應該就是盡量編寫短小精悍的方法,而為你的算法生成高效的機器碼是C#編譯器和JIT編譯器的責任。
小節:
將C#代碼翻譯為可執行的機器碼有兩個步驟:1.C#編譯器將代碼生成為IL,並放在程序集中。2.JIT再根據需要逐一為方法(或是一組方法,如果涉及內聯)生成機器碼。短小的方法讓JIT編譯器能夠更好的平攤編譯的代價。短小的代碼也更適合內聯。方法除了短小之外,簡化控制流程也很重要,控制的分支越少JIT編譯器也更容易選擇找到最適合放在寄存器中的變量。因此,編寫短小精悍的代碼不但影響代碼的可讀性,也影響到程序運行的效率。