當編寫一個小程序來比較傳統foreach
與IEnumerable
上的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
這是處於發佈模式還是調試模式? – learningNew