迭代链接列出的性能C#
本文关键字:性能 链接 迭代 | 更新日期: 2023-09-27 18:28:27
我目前正在做一些需要类似层次结构的设计(很像团结中的设计……事实上,我基本上只是在团结之上复制团结,作为一种学习体验),这需要在列表中插入/移动大量对象。出于这个原因,我决定使用LinkedList来存储所有层次结构对象(这是我第一次使用ListList)。
我决定看看哪种在链表上循环的方法最有效。我知道我可能在微观优化,但我通常通过测试来学习新东西,我的测试结果让我有点惊讶,所以我希望有人能对此有所了解。
(我不是设置性能测试的专家,但这种设置通常能让我很好地了解性能的差异)
测试:列表中有10个整数,另一个有100000
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
private static Stopwatch timer = new Stopwatch();
static void Main(string[] args)
{
/// Initialization
var _linkedList = new LinkedList<int>();
for (int i = 0; i < 10; i++)
_linkedList.AddLast(i);
/// Test 1
timer.Start();
for (var _node = _linkedList.First; _node != _linkedList.Last; _node = _node.Next)
FuncFoo(_node.Value);
timer.Stop();
Console.WriteLine("For Loop: " + timer.Elapsed);
timer.Reset();
/// Test 2
timer.Start();
foreach (var _int in _linkedList)
FuncFoo(_int);
timer.Stop();
Console.WriteLine("Foreach Loop: " + timer.Elapsed);
timer.Reset();
/// Test 3
timer.Start();
var _listNode = _linkedList.First;
while (_listNode != _linkedList.Last)
{
FuncFoo(_listNode.Value);
_listNode = _listNode.Next;
}
timer.Stop();
Console.WriteLine("While Loop: " + timer.Elapsed);
timer.Reset();
///End
Console.Write("Press any key to continue...");
Console.ReadKey();
}
private static void FuncFoo(int _num)
{
_num = (int)Math.Sqrt(1 + 2 + 3 + 4 + 5) * _num;
}
}
}
结果:列表中的10个整数
For Loop: 0.0002371
Foreach Loop: 0.0002880
While Loop: 0.0000002
结果:列表中的100000个整数
For Loop: 0.0013548
Foreach Loop: 0.0015256
While Loop: 0.0013436
所以有两件事我不确定。
为什么for循环比while循环慢得多?我以为for循环在幕后起到了while循环的作用?(我理解为什么foreach循环在所有情况下都稍微慢一点)
为什么while循环的效率会随着链接列表中添加更多元素而降低?我想它们或多或少都会保持相同的比例(就像for&foreach循环一样)。但是看到while循环似乎失去了性能(或者for/foreach增益性能?)让我很困惑。
我假设我的代码作为测试可能是不起作用的(在这种情况下,我想知道我做错了什么/为什么,这样我就知道如何在未来进行更可靠的测试)。但如果这是一个有效的测试,我想知道是什么导致了这些看似奇怪的结果(即使性能差异不会对我的代码产生影响)。
首先,通过将迭代次数从1增加到100万,使您的测试更具代表性。还为每个测试添加未记录的迭代,以使JIT编译器有机会优化我们的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
private static Stopwatch timer = new Stopwatch();
static void Main(string[] args)
{
/// Initialization
var _linkedList = new LinkedList<int>();
for (int i = 0; i < 10; i++)
_linkedList.AddLast(i);
for (int x = 0; x < 1000000; x++)
for (var _node = _linkedList.First; _node != _linkedList.Last; _node = _node.Next)
FuncFoo(_node.Value);
for (int x = 0; x < 1000000; x++)
foreach (var _int in _linkedList)
FuncFoo(_int);
for (int x = 0; x < 1000000; x++)
{
var _listNode = _linkedList.First;
while (_listNode != _linkedList.Last)
{
FuncFoo(_listNode.Value);
_listNode = _listNode.Next;
}
}
/// Test 1
timer.Start();
for (int x = 0; x < 1000000; x++)
for (var _node = _linkedList.First; _node != _linkedList.Last; _node = _node.Next)
FuncFoo(_node.Value);
timer.Stop();
Console.WriteLine("For Loop: " + timer.Elapsed);
timer.Reset();
/// Test 2
timer.Start();
for (int x = 0; x < 1000000; x++)
foreach (var _int in _linkedList)
FuncFoo(_int);
timer.Stop();
Console.WriteLine("Foreach Loop: " + timer.Elapsed);
timer.Reset();
/// Test 3
timer.Start();
for (int x = 0; x < 1000000; x++)
{
var _listNode = _linkedList.First;
while (_listNode != _linkedList.Last)
{
FuncFoo(_listNode.Value);
_listNode = _listNode.Next;
}
}
timer.Stop();
Console.WriteLine("While Loop: " + timer.Elapsed);
timer.Reset();
///End
Console.Write("Press any key to continue...");
Console.ReadKey();
}
private static void FuncFoo(int _num)
{
_num = (int)Math.Sqrt(1 + 2 + 3 + 4 + 5) * _num;
}
}
}
哇!还有更多实际结果:
For Loop: 00:00:00.2793502
Foreach Loop: 00:00:00.3588778
While Loop: 00:00:00.2660378
正如我们所看到的,for循环和while循环具有相同的结果。这是因为他们的MSIL代码也几乎相同:
IL_0115: callvirt instance class [System]System.Collections.Generic.LinkedListNode`1<!0> class [System]System.Collections.Generic.LinkedList`1<int32>::get_First()
IL_011a: stloc.s _listNode
IL_011c: br.s IL_0136
// loop start (head: IL_0136)
IL_011e: nop
IL_011f: ldloc.s _listNode
IL_0121: callvirt instance !0 class [System]System.Collections.Generic.LinkedListNode`1<int32>::get_Value()
IL_0126: call void ConsoleApplication1.Program::FuncFoo(int32)
IL_012b: nop
IL_012c: ldloc.s _listNode
IL_012e: callvirt instance class [System]System.Collections.Generic.LinkedListNode`1<!0> class [System]System.Collections.Generic.LinkedListNode`1<int32>::get_Next()
IL_0133: stloc.s _listNode
IL_0135: nop
IL_0136: ldloc.s _listNode
IL_0138: ldloc.0
IL_0139: callvirt instance class [System]System.Collections.Generic.LinkedListNode`1<!0> class [System]System.Collections.Generic.LinkedList`1<int32>::get_Last()
IL_013e: ceq
IL_0140: ldc.i4.0
IL_0141: ceq
IL_0143: stloc.s CS$4$0000
IL_0145: ldloc.s CS$4$0000
IL_0147: brtrue.s IL_011e
// end loop
ForEach循环运行时间更长,因为它的机制创建了实现IDisposable的Enumerator的新实例,并且应该在循环的末尾进行处理。实际MSIL代码如下所示:
IL_009c: ldloc.0
IL_009d: callvirt instance valuetype [System]System.Collections.Generic.LinkedList`1/Enumerator<!0> class [System]System.Collections.Generic.LinkedList`1<int32>::GetEnumerator()
IL_00a2: stloc.s CS$5$0001
.try
{
IL_00a4: br.s IL_00b5
// loop start (head: IL_00b5)
IL_00a6: ldloca.s CS$5$0001
IL_00a8: call instance !0 valuetype [System]System.Collections.Generic.LinkedList`1/Enumerator<int32>::get_Current()
IL_00ad: stloc.3
IL_00ae: ldloc.3
IL_00af: call void ConsoleApplication1.Program::FuncFoo(int32)
IL_00b4: nop
IL_00b5: ldloca.s CS$5$0001
IL_00b7: call instance bool valuetype [System]System.Collections.Generic.LinkedList`1/Enumerator<int32>::MoveNext()
IL_00bc: stloc.s CS$4$0000
IL_00be: ldloc.s CS$4$0000
IL_00c0: brtrue.s IL_00a6
// end loop
IL_00c2: leave.s IL_00d3
} // end .try
finally
{
IL_00c4: ldloca.s CS$5$0001
IL_00c6: constrained. valuetype [System]System.Collections.Generic.LinkedList`1/Enumerator<int32>
IL_00cc: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_00d1: nop
IL_00d2: endfinally
} // end handler
正如您所看到的,这个MSIL完全不同,因为它使用了Enumerator.MoveNext方法而不是LinkedListNode.get_Next(),并创建了影响性能的try块,因为在某些平台上,异常处理会带来成本。
此外,JIT不会对"try"块执行优化,这也会影响您的性能。