哪个代码段可以提供更好的性能?以下代码段是用C#编写的。
1。
for(int counter=0; counter<list.Count; counter++)
{
list[counter].DoSomething();
}
2。
foreach(MyType current in list)
{
current.DoSomething();
}
list
确实有一个count
不是的成员Count
。
哪个代码段可以提供更好的性能?以下代码段是用C#编写的。
1。
for(int counter=0; counter<list.Count; counter++)
{
list[counter].DoSomething();
}
2。
foreach(MyType current in list)
{
current.DoSomething();
}
list
确实有一个count
不是的成员Count
。
Answers:
好吧,部分取决于的确切类型list
。它还取决于您使用的确切CLR。
它是否有意义,将取决于您是否在循环中进行任何实际工作。在几乎所有情况下,性能上的差异都不是很大,但可读性上的差异则有利于foreach
循环。
我个人也会使用LINQ来避免出现“ if”:
foreach (var item in list.Where(condition))
{
}
编辑:对于那些声称对List<T>
with进行迭代foreach
产生与for
循环相同代码的人来说,以下证据表明它不会:
static void IterateOverList(List<object> list)
{
foreach (object o in list)
{
Console.WriteLine(o);
}
}
产生IL:
.method private hidebysig static void IterateOverList(class [mscorlib]System.Collections.Generic.List`1<object> list) cil managed
{
// Code size 49 (0x31)
.maxstack 1
.locals init (object V_0,
valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object> V_1)
IL_0000: ldarg.0
IL_0001: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<object>::GetEnumerator()
IL_0006: stloc.1
.try
{
IL_0007: br.s IL_0017
IL_0009: ldloca.s V_1
IL_000b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::get_Current()
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: call void [mscorlib]System.Console::WriteLine(object)
IL_0017: ldloca.s V_1
IL_0019: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::MoveNext()
IL_001e: brtrue.s IL_0009
IL_0020: leave.s IL_0030
} // end .try
finally
{
IL_0022: ldloca.s V_1
IL_0024: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>
IL_002a: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_002f: endfinally
} // end handler
IL_0030: ret
} // end of method Test::IterateOverList
编译器对数组的处理方式不同,foreach
基本上将for
循环转换为循环,但不是List<T>
。这是数组的等效代码:
static void IterateOverArray(object[] array)
{
foreach (object o in array)
{
Console.WriteLine(o);
}
}
// Compiles into...
.method private hidebysig static void IterateOverArray(object[] 'array') cil managed
{
// Code size 27 (0x1b)
.maxstack 2
.locals init (object V_0,
object[] V_1,
int32 V_2)
IL_0000: ldarg.0
IL_0001: stloc.1
IL_0002: ldc.i4.0
IL_0003: stloc.2
IL_0004: br.s IL_0014
IL_0006: ldloc.1
IL_0007: ldloc.2
IL_0008: ldelem.ref
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: call void [mscorlib]System.Console::WriteLine(object)
IL_0010: ldloc.2
IL_0011: ldc.i4.1
IL_0012: add
IL_0013: stloc.2
IL_0014: ldloc.2
IL_0015: ldloc.1
IL_0016: ldlen
IL_0017: conv.i4
IL_0018: blt.s IL_0006
IL_001a: ret
} // end of method Test::IterateOverArray
有趣的是,我在任何地方都找不到C#3规范中记录的内容...
List<T>
。
foreach
在数组上等效for
。始终始终为提高可读性编写代码,然后只有在有证据表明它可以带来可衡量的性能优势时才进行微优化。
一个for
循环被编译代码约相当于此:
int tempCount = 0;
while (tempCount < list.Count)
{
if (list[tempCount].value == value)
{
// Do something
}
tempCount++;
}
其中,foreach
循环被编译为大致等效于此的代码:
using (IEnumerator<T> e = list.GetEnumerator())
{
while (e.MoveNext())
{
T o = (MyClass)e.Current;
if (row.value == value)
{
// Do something
}
}
}
如您所见,这将取决于枚举器的实现方式以及列表索引器的实现方式。事实证明,基于数组的类型的枚举数通常是这样写的:
private static IEnumerable<T> MyEnum(List<T> list)
{
for (int i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
如您所见,在这种情况下,它不会有太大的区别,但是链表的枚举数可能看起来像这样:
private static IEnumerable<T> MyEnum(LinkedList<T> list)
{
LinkedListNode<T> current = list.First;
do
{
yield return current.Value;
current = current.Next;
}
while (current != null);
}
在.NET中,您会发现LinkedList <T>类甚至没有索引器,因此您将无法在链表上进行for循环;但是如果可以的话,索引器的编写必须像这样:
public T this[int index]
{
LinkedListNode<T> current = this.First;
for (int i = 1; i <= index; i++)
{
current = current.Next;
}
return current.value;
}
如您所见,在循环中多次调用此方法比使用可以记住它在列表中位置的枚举器要慢得多。
一个简单的测试来半验证。我做了一个小测试,只是看看。这是代码:
static void Main(string[] args)
{
List<int> intList = new List<int>();
for (int i = 0; i < 10000000; i++)
{
intList.Add(i);
}
DateTime timeStarted = DateTime.Now;
for (int i = 0; i < intList.Count; i++)
{
int foo = intList[i] * 2;
if (foo % 2 == 0)
{
}
}
TimeSpan finished = DateTime.Now - timeStarted;
Console.WriteLine(finished.TotalMilliseconds.ToString());
Console.Read();
}
这是foreach部分:
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
当我用foreach代替for时-foreach的速度快了20毫秒- 始终如一。for为135-139ms,而foreach为113-119ms。我来回交换了好几次,以确保它不是刚刚启动的某个过程。
但是,当我删除foo和if语句时,for加快了30 ms(foreach为88ms,for为59ms)。他们都是空壳。我假设foreach实际上传递了一个变量,而for只是在增加一个变量。如果我加了
int foo = intList[i];
然后for变慢大约30ms。我假设这与创建foo并获取数组中的变量并将其分配给foo有关。如果您只是访问intList [i],那么您就不会受到惩罚。
老实说,我希望在所有情况下,foreach都会稍微慢一些,但在大多数应用程序中还不够重要。
编辑:这是使用Jons建议的新代码(134217728是在引发System.OutOfMemory异常之前可以拥有的最大int):
static void Main(string[] args)
{
List<int> intList = new List<int>();
Console.WriteLine("Generating data.");
for (int i = 0; i < 134217728 ; i++)
{
intList.Add(i);
}
Console.Write("Calculating for loop:\t\t");
Stopwatch time = new Stopwatch();
time.Start();
for (int i = 0; i < intList.Count; i++)
{
int foo = intList[i] * 2;
if (foo % 2 == 0)
{
}
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
Console.Write("Calculating foreach loop:\t");
time.Reset();
time.Start();
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
Console.Read();
}
结果如下:
生成数据。计算for循环:2458ms计算foreach循环:2005ms
交换它们以查看它是否处理事物的顺序会产生相同的结果(几乎)。
注意:这个答案比Java适用于Java的要多得多,因为C#上没有索引器LinkedLists
,但是我认为一般意义仍然成立。
如果list
您使用的恰好是a LinkedList
,则对于大型列表,索引器代码(数组样式访问)的性能要比使用IEnumerator
from中的差很多foreach
。
当您LinkedList
使用indexer语法:来访问元素10.000时list[10000]
,链表将从头节点开始,并遍历Next
-pointer一千次,直到到达正确的对象。显然,如果您循环执行此操作,则会得到:
list[0]; // head
list[1]; // head.Next
list[2]; // head.Next.Next
// etc.
调用时GetEnumerator
(隐式使用forach
-syntax),您将获得一个IEnumerator
具有指向头节点的指针的对象。每次调用时MoveNext
,该指针将移至下一个节点,如下所示:
IEnumerator em = list.GetEnumerator(); // Current points at head
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
// etc.
如您所见,在使用LinkedList
s 的情况下,循环索引的方法变得越来越慢,循环的时间也越来越长(它必须一遍又一遍地通过相同的头指针)。尽管IEnumerable
只是工作在固定的时间。
当然,正如乔恩所说,这实际上取决于的类型list
,如果list
不是一个LinkedList
,而是一个数组,其行为则完全不同。
LinkedList<T>
MSDN上的文档,它有一个相当不错的API。最重要的是,它没有get(int index)
像Java这样的方法。不过,我想对于其他任何类似列表的数据结构来说,这一点仍然成立,因为该数据结构公开的索引器比特定的慢IEnumerator
。
就像其他人提到的那样,尽管性能实际上并不重要,但由于循环中的IEnumerable
/ IEnumerator
用法,foreach总是会稍慢一些。编译器将构造转换为该接口上的调用,并在foreach构造中为每个步骤调用一个函数+一个属性。
IEnumerator iterator = ((IEnumerable)list).GetEnumerator();
while (iterator.MoveNext()) {
var item = iterator.Current;
// do stuff
}
这是C#中结构的等效扩展。您可以想象对性能的影响如何根据MoveNext和Current的实现而变化。而在数组访问中,您没有那种依赖关系。
List<T>
此处,则仍然存在调用索引器的命中(可能是内联的)。这不像是裸机阵列访问。
在您提供的示例中,绝对最好使用foreach
循环而不是for
循环。
除非循环已展开(每步1.0个循环),否则标准foreach
构造比简单的for-loop
(每步2个循环)更快(每步1.5个循环)。
因此,对于日常的代码,性能是不是一个理由使用更复杂的for
,while
或do-while
结构。
查看此链接:http : //www.codeproject.com/Articles/146797/Fast-and-Less-Fast-Loops-in-C
╔══════════════════════╦═══════════╦═══════╦════════════════════════╦═════════════════════╗
║ Method ║ List<int> ║ int[] ║ Ilist<int> onList<Int> ║ Ilist<int> on int[] ║
╠══════════════════════╬═══════════╬═══════╬════════════════════════╬═════════════════════╣
║ Time (ms) ║ 23,80 ║ 17,56 ║ 92,33 ║ 86,90 ║
║ Transfer rate (GB/s) ║ 2,82 ║ 3,82 ║ 0,73 ║ 0,77 ║
║ % Max ║ 25,2% ║ 34,1% ║ 6,5% ║ 6,9% ║
║ Cycles / read ║ 3,97 ║ 2,93 ║ 15,41 ║ 14,50 ║
║ Reads / iteration ║ 16 ║ 16 ║ 16 ║ 16 ║
║ Cycles / iteration ║ 63,5 ║ 46,9 ║ 246,5 ║ 232,0 ║
╚══════════════════════╩═══════════╩═══════╩════════════════════════╩═════════════════════╝
您可以在Deep .NET-第1部分迭代中了解它
它涵盖了.NET源代码一直到反汇编的结果(没有第一次初始化)。