2015-07-20 28 views
1

當編寫一個小程序來比較傳統foreachIEnumerable上的LINQ .ToList().ForEach()的性能時,我提取了一個小的虛擬方法,以便能夠快速更改我想測試的操作。這時候,我突然發現我的測量時間的下降,所以這是一個小班,我創建進一步測試:使用抽取方法時,爲什麼會出現性能下降?

class Dummy 
{ 
    public void Iterate() 
    { 
    Stopwatch sw = Stopwatch.StartNew(); 

    foreach (int n in Enumerable.Range(0, 50000000)) 
    { 
     int dummy = n/2; 
    } 

    sw.Stop(); 
    Console.WriteLine("Iterate took {0}ms.", sw.ElapsedMilliseconds); 
    } 

    public void IterateWithMethodCall() 
    { 
    Stopwatch sw = Stopwatch.StartNew(); 

    foreach (int n in Enumerable.Range(0, 50000000)) 
    { 
     SomeOperation(n); 
    } 

    sw.Stop(); 
    Console.WriteLine("IterateWithMethodCall took {0}ms.", sw.ElapsedMilliseconds); 
    } 

    private void SomeOperation(int n) 
    { 
    int dummy = n/2; 
    } 
} 

這是入口點:

public static void Main(string[] args) 
{ 
    Dummy dummy = new Dummy(); 
    dummy.Iterate(); 
    dummy.IterateWithMethodCall(); 
    Console.ReadKey(); 
} 

輸出我得到在我的機器上看起來像這樣:

迭代花了534ms。

IterateWithMethodCall花了1256ms。

這是什麼原因?我的猜測是,程序必須在每個步驟中「跳」到SomeOperation方法來執行代碼,因此失去了時間,但我更喜歡更嚴格的解釋(任何參考鏈接關於如何正確運行也是受歡迎的) 。

這是否意味着我不應該重構更復雜的操作:將代碼片段提取到更小的方法中,以便在需要性能的每一點時?

編輯:我看着由此產生的IL代碼,並在循環(釋放模式)有所不同;也許有人可以解釋這一點,我自己也無法這樣做。這僅是在循環的代碼的其餘部分是相同的:

IterateWithMethodCall:

IL_0017: br.s IL_0027 
    // loop start (head: IL_0027) 
     IL_0019: ldloc.2 
     IL_001a: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current() 
     IL_001f: stloc.1 
     IL_0020: ldarg.0 
     IL_0021: ldloc.1 
     IL_0022: call instance void WithoutStatic.Dummy::SomeOperation(int32) 

     IL_0027: ldloc.2 
     IL_0028: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() 
     IL_002d: brtrue.s IL_0019 
    // end loop 

迭代:

IL_0017: br.s IL_0024 
    // loop start (head: IL_0024) 
     IL_0019: ldloc.2 
     IL_001a: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current() 
     IL_001f: stloc.1 
     IL_0020: ldloc.1 
     IL_0021: ldc.i4.2 
     IL_0022: div 
     IL_0023: pop 

     IL_0024: ldloc.2 
     IL_0025: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() 
     IL_002a: brtrue.s IL_0019 
    // end loop 
+0

這是處於發佈模式還是調試模式? – learningNew

回答

1

操作時間的差異是很容易解釋。每次調用某個方法時,CLR-Runtime都必須跳轉到已編譯的CLI代碼中的定義,才能執行該方法。但這不是主要的。此外,運行時必須在方法調用中創建新的作用域,其中每個變量和參數都必須存儲。即使創建和發佈示波器速度非常快,在您的範圍內,您可以識別時間。

它們也是調試和發佈模式的區別。編譯器可以識別他是否可以嵌入一個簡單的方法,因此代碼優化將刪除您的方法並直接在您的循環中替換代碼。

希望這會有所幫助。

+0

啊,是的,我忘了在執行時間時更改爲釋放模式(我經常忘記這個......)。只要我改變到發佈模式,我會獲得很多,但它仍然大約是150ms。即使將代碼嵌入循環中,編譯器是否仍然創建新的作用域? – InvisiblePanda

+0

不,他沒有,這就是爲什麼它更快的原因之一。 – BendEg

+0

但是,在發佈模式下它不應該大致相同的執行時間,而不是那個區別?還是我在某個地方誤解了你?那就是爲什麼還有差距? – InvisiblePanda

相關問題