C++

C# 迴圈的判斷會進來幾次

Google+ Pinterest LinkedIn Tumblr

最近有小夥伴告訴我,在迴圈的判斷條件只會計算一次,本金魚不相信,於是就做了測試,本文記錄我做的測試。

先來寫一個簡單的程式碼, 就一個迴圈,迴圈的判斷是從一個函式獲取值

class Program
    {
        static void Main(string[] args)
        {
            var meepeMorcear = new MeepeMorcear();
            meepeMorcear.BirmeruLerrayjairbay();
        }
    }

    class MeepeMorcear
    {
        public void BirmeruLerrayjairbay()
        {
            for (int i = 0; i < DaydrearNenawerlai(); i++)
            {
                Console.WriteLine("第" +i.ToString()+"個逗比");
            }
        }

        public int DaydrearNenawerlai()
        {
            Console.WriteLine("進入");
            return 10;
        }
    }

通過 Main 呼叫 BirmeruLerrayjairbay ,這個函式裏面的 for 判斷是 DaydrearNenawerlai 拿到一個值,我嘗試在 release 執行,結果每個判斷都需要進入 DaydrearNenawerlai 函式,請看輸出

進入
第0個逗比
進入
第1個逗比
進入
第2個逗比
進入
第3個逗比
進入
第4個逗比
進入
第5個逗比
進入
第6個逗比
進入
第7個逗比
進入
第8個逗比
進入
第9個逗比

也就是在 Debug 或 Release 下,for 裏面的判斷都是需要執行,所以在 for 裡的判斷如果寫了很長的計算,那麼就會在每次迴圈都需要重新計算。即使每次計算出來的值都是一樣,也需要重新計算。

所以這樣看起來效能不如這樣寫,使用一個臨時的變數獲取判斷的值

public void BirmeruLerrayjairbay()
        {
            var mowraiTepalor = DaydrearNenawerlai();
            for (int i = 0; i < mowraiTepalor; i++)
            {
                Console.WriteLine("第" +i.ToString()+"個逗比");
            }
        }

但是很快,另一個小夥伴告訴我,你把輸出去掉,然後使用斷點,你再看看

C# 迴圈的判斷會進來幾次

我新增了斷點,在斷點輸出 123 然後執行

C# 迴圈的判斷會進來幾次

這時我發現執行沒有輸出 123 也就是函式沒有進來,我再次新增斷點,跟著函式也沒有訪問

所以這時的 DaydrearNenawerlai 函式就被優化掉了

我和一個小夥伴說了這個問題,他說是被 IL 優化了,我一點不相信,所以我就去看 IL 程式碼

從下面的程式碼

public void BirmeruLerrayjairbay()
        {
            for (int i = 0; i < DaydrearNenawerlai(); i++)
            {
                Console.WriteLine("第" +i.ToString()+"個逗比");
            }
        }

        /// <summary>
        /// 進入lindexi.github.io可以看到更多部落格
        /// </summary>
        /// <returns></returns>
        public static int DaydrearNenawerlai()
        {
            return 10;
        }

轉 IL 可以看到下面程式碼,我會在 IL 新增很多註釋,所以很容易看懂。

.method public hidebysig instance void 
    BirmeruLerrayjairbay() cil managed 
  {
    .maxstack 3
    .locals init (
      [0] int32 i
    )
    
    // 第 23 行 18 個字元到 27 個字元
    // [23 18 - 23 27]
    IL_0000: ldc.i4.0  
    // 定義 i ,程式碼的 int i = 0;   
    IL_0001: stloc.0      // i

    IL_0002: br.s         IL_0023
    //  這裏就是進入迴圈 for ,在 IL 不管 for 還是 while 都是差不多
    // start of loop, entry point: IL_0023

      // [25 17 - 25 60]
      // 下面這個程式碼就是 Console.WriteLine("第" +i.ToString()+"個逗比"); 從程式碼可以看到
      // 需要先申請"第"
      IL_0004: ldstr        "第"
      // 然後把 i 放入棧
      IL_0009: ldloca.s     i
      // 呼叫 int.ToString ,使用的是例項的方法
      IL_000b: call         instance string [mscorlib]System.Int32::ToString()
      // 把"個逗比"放入棧
      IL_0010: ldstr        "個逗比"
      // 呼叫字串組合方法,組合三個字串,返回一個字串。
      // 把剛纔入棧三個字串出棧,返回的字串入棧
      IL_0015: call         string [mscorlib]System.String::Concat(string, string, string)
      // 呼叫 Console.WriteLine ,從棧拿到一個字串輸出
      IL_001a: call         void [mscorlib]System.Console::WriteLine(string)

      // 下面是 i++ 程式碼
      // [23 55 - 23 58]
      // 將指定索引處的區域性變數載入到計算堆疊上,這裏的索引是 0 ,在程式碼的變數是 i 所以把 i 載入到計算堆疊
      IL_001f: ldloc.0      // i
      // 將整數值 1 作為 int32 推送到計算堆疊上
      IL_0020: ldc.i4.1     
      // 從堆疊出棧兩個數值進行相加,返回的值放在棧
      IL_0021: add          
      // 從計算堆疊的頂部彈出當前值並將其儲存到指定索引處的區域性變數列表中,這裏索引是 0 ,在程式碼的變數是 i ,所以 i = i + 1 的程式碼就是這樣做
      IL_0022: stloc.0      // i
 
      // 從堆疊載入 i ,把 i 入棧
      // [23 29 - 23 53]
      IL_0023: ldloc.0      // i
      // 呼叫方法 DaydrearNenawerlai 拿到返回值
      IL_0024: call         int32 MuhoubearWhedoofi.MeepeMorcear::DaydrearNenawerlai()
      // 如果第一個值小於第二個值,則將控制轉移到目標指令,這裏的第一個值就是 i ,第二個值就是 DaydrearNenawerlai 的返回值。跳轉到標籤 IL_0004 ,如果沒有小於,就繼續程式碼。
      IL_0029: blt.s        IL_0004
    // end of loop

    // [27 9 - 27 10]
    IL_002b: ret          

  } // end of method MeepeMorcear::BirmeruLerrayjairbay

  .method public hidebysig static int32 
    DaydrearNenawerlai() cil managed 
  {
    .maxstack 8

    // 把一個值 放入堆疊,放入的是 10 ,然後從棧拿到值返回
    // [36 13 - 36 23]
    IL_0000: ldc.i4.s     10 // 0x0a
    IL_0002: ret          

  } // end of method MeepeMorcear::DaydrearNenawerlai

從上面程式碼可以發現,實際 DaydrearNenawerlai 沒有被優化掉,還是有這個方法。

Write A Comment