在c# / VB.NET/。哪个循环运行得更快,for还是foreach?
自从很久以前我读到for循环比foreach循环工作得快,我就认为它适用于所有集合、泛型集合、所有数组等。
我搜索了谷歌,找到了几篇文章,但大多数都是不确定的(阅读文章评论),而且是开放式的。
理想的情况是列出每种情况以及最佳解决方案。
例如(这只是一个例子):
用于迭代1000+的数组
字符串- for比foreach好
对于迭代IList(非泛型)字符串- foreach更好
比
在网上找到了一些相同的参考资料:
由Emmanuel Schanzer撰写的原创文章
CodeProject FOREACH Vs. FOR
博客——去博客还是不去博客,这是个问题
ASP。NET论坛- NET 1.1 c# for vs foreach
(编辑)
除了可读性之外,我对事实和数据真的很感兴趣。在某些应用中,最后一英里的性能优化确实很重要。
当你遍历常见的结构如数组、列表等时,for-和foreach-循环的速度差异很小,并且在集合上执行LINQ查询几乎总是稍微慢一些,尽管它写得更好!正如其他海报上说的那样,追求表现力而不是多出一毫秒的性能。
到目前为止还没有说的是,当编译foreach循环时,编译器会根据它迭代的集合对它进行优化。这意味着当你不确定使用哪个循环时,你应该使用foreach循环——它会在编译时为你生成最好的循环。它的可读性也更强。
foreach循环的另一个关键优势是,如果你的集合实现发生了变化(例如,从int数组到List<int>),那么你的foreach循环将不需要任何代码更改:
foreach (int i in myCollection)
不管你的集合是什么类型,上面的都是一样的,而在你的for循环中,如果你把myCollection从数组改变为List,下面的将不会构建:
for (int i = 0; i < myCollection.Length, i++)
首先是对德米特里的回答(现已删除)的反诉。对于数组,c#编译器为foreach生成的代码与等效的For循环生成的代码大致相同。这就解释了为什么这个基准测试的结果基本相同:
using System;
using System.Diagnostics;
using System.Linq;
class Test
{
const int Size = 1000000;
const int Iterations = 10000;
static void Main()
{
double[] data = new double[Size];
Random rng = new Random();
for (int i=0; i < data.Length; i++)
{
data[i] = rng.NextDouble();
}
double correctSum = data.Sum();
Stopwatch sw = Stopwatch.StartNew();
for (int i=0; i < Iterations; i++)
{
double sum = 0;
for (int j=0; j < data.Length; j++)
{
sum += data[j];
}
if (Math.Abs(sum-correctSum) > 0.1)
{
Console.WriteLine("Summation failed");
return;
}
}
sw.Stop();
Console.WriteLine("For loop: {0}", sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i=0; i < Iterations; i++)
{
double sum = 0;
foreach (double d in data)
{
sum += d;
}
if (Math.Abs(sum-correctSum) > 0.1)
{
Console.WriteLine("Summation failed");
return;
}
}
sw.Stop();
Console.WriteLine("Foreach loop: {0}", sw.ElapsedMilliseconds);
}
}
结果:
For loop: 16638
Foreach loop: 16529
接下来,验证Greg关于集合类型的观点是重要的——在上面将数组更改为List<double>,您将得到完全不同的结果。它不仅在一般情况下要慢得多,而且foreach也比通过索引访问慢得多。话虽如此,我仍然几乎总是喜欢foreach而不是for循环,因为它使代码更简单——因为可读性几乎总是重要的,而微优化很少是。
Jeffrey Richter在techhed 2005上说:
"I have come to learn over the years the C# compiler is basically a liar to me." .. "It lies about many things." .. "Like when you do a foreach loop..." .. "...that is one little line of code that you write, but what the C# compiler spits out in order to do that it's phenomenal. It puts out a try/finally block in there, inside the finally block it casts your variable to an IDisposable interface, and if the cast suceeds it calls the Dispose method on it, inside the loop it calls the Current property and the MoveNext method repeatedly inside the loop, objects are being created underneath the covers. A lot of people use foreach because it's very easy coding, very easy to do.." .. "foreach is not very good in terms of performance, if you iterated over a collection instead by using square bracket notation, just doing index, that's just much faster, and it doesn't create any objects on the heap..."
按需网络直播:
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032292286&EventCategory=3&culture=en-US&CountryCode=US
Foreach循环比for循环展示了更具体的意图。
使用foreach循环向使用您代码的任何人表明,您计划对集合中的每个成员执行一些操作,而不管其在集合中的位置。它还显示您没有修改原始集合(如果您试图修改,则会抛出异常)。
foreach的另一个优点是它适用于任何IEnumerable,而as for只适用于IList,其中每个元素实际上都有一个索引。
但是,如果需要使用元素的索引,那么当然应该允许使用for循环。但是如果您不需要使用索引,那么使用索引只会使您的代码变得混乱。
据我所知,这对性能没有重大影响。在未来的某个阶段,使用foreach调整代码以在多核上运行可能会更容易,但现在还不需要担心这一点。
每当有关于性能的争论时,您只需要编写一个小测试,以便您可以使用量化结果来支持您的案例。
使用StopWatch类,为了精确起见,重复某件事几百万次。(如果没有for循环,这可能很难):
using System.Diagnostics;
//...
Stopwatch sw = new Stopwatch()
sw.Start()
for(int i = 0; i < 1000000;i ++)
{
//do whatever it is you need to time
}
sw.Stop();
//print out sw.ElapsedMilliseconds
幸运的是,这样做的结果表明差异可以忽略不计,您还可以在最可维护的代码中执行任何结果
每种语言结构都有适当的使用时间和地点。c#语言有四个单独的迭代语句是有原因的——每个语句都有特定的目的,并且有适当的用法。
我建议你和你的老板坐下来,试着理性地解释为什么for循环有一个目的。有时for迭代块比foreach迭代块更清楚地描述算法。在这种情况下,使用它们是合适的。
我还要向你的老板指出——性能不是,也不应该是任何实际方式的问题——这更像是用简洁、有意义、可维护的方式表达算法的问题。这样的微优化完全忽略了性能优化的要点,因为任何真正的性能好处都来自算法重新设计和重构,而不是循环重构。
如果在理性的讨论之后,仍然有这种权威主义的观点,那就取决于你如何继续下去了。就我个人而言,我不会喜欢在一个不鼓励理性思考的环境中工作,我会考虑跳槽到另一个雇主手下。然而,我强烈建议在感到不安之前讨论一下——这可能只是一个简单的误解。
我发现foreach循环迭代列表更快。下面是我的测试结果。在下面的代码中,我分别迭代一个大小为100、10000和100000的数组,使用for和foreach循环来测量时间。
private static void MeasureTime()
{
var array = new int[10000];
var list = array.ToList();
Console.WriteLine("Array size: {0}", array.Length);
Console.WriteLine("Array For loop ......");
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < array.Length; i++)
{
Thread.Sleep(1);
}
stopWatch.Stop();
Console.WriteLine("Time take to run the for loop is {0} millisecond", stopWatch.ElapsedMilliseconds);
Console.WriteLine(" ");
Console.WriteLine("Array Foreach loop ......");
var stopWatch1 = Stopwatch.StartNew();
foreach (var item in array)
{
Thread.Sleep(1);
}
stopWatch1.Stop();
Console.WriteLine("Time take to run the foreach loop is {0} millisecond", stopWatch1.ElapsedMilliseconds);
Console.WriteLine(" ");
Console.WriteLine("List For loop ......");
var stopWatch2 = Stopwatch.StartNew();
for (int i = 0; i < list.Count; i++)
{
Thread.Sleep(1);
}
stopWatch2.Stop();
Console.WriteLine("Time take to run the for loop is {0} millisecond", stopWatch2.ElapsedMilliseconds);
Console.WriteLine(" ");
Console.WriteLine("List Foreach loop ......");
var stopWatch3 = Stopwatch.StartNew();
foreach (var item in list)
{
Thread.Sleep(1);
}
stopWatch3.Stop();
Console.WriteLine("Time take to run the foreach loop is {0} millisecond", stopWatch3.ElapsedMilliseconds);
}
更新
在@jgauffin建议后,我使用了@johnskeet代码,发现使用数组的for循环比下面的更快,
Foreach循环与数组。
For带列表的循环。
Foreach循环与列表。
请看下面我的测试结果和代码,
private static void MeasureNewTime()
{
var data = new double[Size];
var rng = new Random();
for (int i = 0; i < data.Length; i++)
{
data[i] = rng.NextDouble();
}
Console.WriteLine("Lenght of array: {0}", data.Length);
Console.WriteLine("No. of iteration: {0}", Iterations);
Console.WriteLine(" ");
double correctSum = data.Sum();
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < Iterations; i++)
{
double sum = 0;
for (int j = 0; j < data.Length; j++)
{
sum += data[j];
}
if (Math.Abs(sum - correctSum) > 0.1)
{
Console.WriteLine("Summation failed");
return;
}
}
sw.Stop();
Console.WriteLine("For loop with Array: {0}", sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (var i = 0; i < Iterations; i++)
{
double sum = 0;
foreach (double d in data)
{
sum += d;
}
if (Math.Abs(sum - correctSum) > 0.1)
{
Console.WriteLine("Summation failed");
return;
}
}
sw.Stop();
Console.WriteLine("Foreach loop with Array: {0}", sw.ElapsedMilliseconds);
Console.WriteLine(" ");
var dataList = data.ToList();
sw = Stopwatch.StartNew();
for (int i = 0; i < Iterations; i++)
{
double sum = 0;
for (int j = 0; j < dataList.Count; j++)
{
sum += data[j];
}
if (Math.Abs(sum - correctSum) > 0.1)
{
Console.WriteLine("Summation failed");
return;
}
}
sw.Stop();
Console.WriteLine("For loop with List: {0}", sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i = 0; i < Iterations; i++)
{
double sum = 0;
foreach (double d in dataList)
{
sum += d;
}
if (Math.Abs(sum - correctSum) > 0.1)
{
Console.WriteLine("Summation failed");
return;
}
}
sw.Stop();
Console.WriteLine("Foreach loop with List: {0}", sw.ElapsedMilliseconds);
}
这和大多数“哪个更快”的问题有相同的两个答案:
1)如果你不测量,你就不知道。
2)(因为…)视情况而定。
这取决于“MoveNext()”方法的代价,相对于“this[int index]”方法的代价,对于你要迭代的IEnumerable的类型(或类型)。
“foreach”关键字是一系列操作的简写——它在IEnumerable上调用GetEnumerator()一次,每次迭代调用MoveNext()一次,它做一些类型检查,等等。最可能影响性能度量的是MoveNext()的成本,因为它被调用了O(N)次。可能便宜,但也可能不便宜。
“for”关键字看起来更容易预测,但在大多数“for”循环中,你会发现类似“collection[index]”这样的东西。这看起来像是一个简单的数组索引操作,但它实际上是一个方法调用,其开销完全取决于迭代的集合的性质。可能便宜,但也可能不便宜。
如果集合的底层结构本质上是一个链表,MoveNext是非常便宜的,但是索引器可能有O(N)成本,使得“for”循环的真正成本为O(N*N)。
我遇到了一个案子,foreach比For快得多
为什么foreach在读取richtextbox行时比for循环快
我有一个类似于那个问题中的OP的案例。
A textbox reading about 72K lines, and I was accessling the Lines property(which is actually a getter method). (And apparently often in winforms there are getter methods that aren't O(1). I suppose it's O(n), so the larger the textbox the longer it takes to get a value from that 'property'. And in the for loop I had as the OP there had for(int i=0;i<textBox1.lines.length;i++) str=textBox1.Lines[i] , and it was really quite slow as it was reading the entire textbox each time it read a line plus it was reading the entire textbox each time it checked the condition.
Jon Skeet演示了您可以只访问一次Lines属性(甚至不是每次迭代一次,只是一次)。而不是每次迭代两次(这是大量的次数)。Do string[] strarlines = textBox1.Lines;然后在星线间循环。
但是一个直观形式的for循环访问Lines属性是非常低效的
for (int i = 0; i < richTextBox.Lines.Length; i++)
{
s = richTextBox.Lines[i];
}
对于文本框,或者富文本框,它非常慢。
OP在一个富文本框上测试了这个循环,发现“有15000行。For循环花了8分钟才循环到15000行。而foreach只花了不到一秒钟的时间来列举它。”
那个链接的OP发现这个foreach比上面提到的他(同一个OP)的for循环要有效得多。就像我一样。
String s=String.Empty;
foreach(string str in txtText.Lines)
{
s=str;
}
internal static void Test()
{
int LOOP_LENGTH = 10000000;
Random random = new Random((int)DateTime.Now.ToFileTime());
{
Dictionary<int, int> dict = new Dictionary<int, int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i, i);
}
for (int i = 0; i < LOOP_LENGTH; i++)
{
for (int k = 0; k < dict.Count; k++)
{
if (dict[k] > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"Dictionary for T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");
GC.Collect();
}
{
Dictionary<int, int> dict = new Dictionary<int, int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i, i);
}
for (int i = 0; i < LOOP_LENGTH; i++)
{
foreach (var item in dict)
{
if (item.Value > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"Dictionary foreach T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");
GC.Collect();
}
{
Dictionary<int, int> dict = new Dictionary<int, int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i, i);
}
for (int i = 0; i < LOOP_LENGTH; i++)
{
foreach (var item in dict.Values)
{
if (item > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"Dictionary foreach values T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");
GC.Collect();
}
{
List<int> dict = new List<int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i);
}
for (int i = 0; i < LOOP_LENGTH; i++)
{
for (int k = 0; k < dict.Count; k++)
{
if (dict[k] > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"list for T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");
GC.Collect();
}
{
List<int> dict = new List<int>();
long first_memory = GC.GetTotalMemory(true);
var stopWatch = Stopwatch.StartNew();
for (int i = 0; i < 64; i++)
{
dict.Add(i);
}
for (int i = 0; i < LOOP_LENGTH; i++)
{
foreach (var item in dict)
{
if (item > 1000000) Console.WriteLine("Test");
}
}
stopWatch.Stop();
var last_memory = GC.GetTotalMemory(true);
Console.WriteLine($"list foreach T:{stopWatch.Elapsed.TotalSeconds}s\t M:{last_memory - first_memory}");
GC.Collect();
}
}
T:10.1957728s M:2080的字典
字典T:10.5900586 M:1952
字典foreach值T:3.8294776s M:2088
T:3.7981471s M:320
T:4.4861377s M:648
一种强大而精确的测量时间的方法是使用BenchmarkDotNet库。
在下面的示例中,我在for/foreach上对1,000,000,000个整数记录进行了循环,并使用BenchmarkDotNet进行了测量:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
public class Program
{
public static void Main()
{
BenchmarkRunner.Run<LoopsBenchmarks>();
}
}
[MemoryDiagnoser]
public class LoopsBenchmarks
{
private List<int> arr = Enumerable.Range(1, 1_000_000_000).ToList();
[Benchmark]
public void For()
{
for (int i = 0; i < arr.Count; i++)
{
int item = arr[i];
}
}
[Benchmark]
public void Foreach()
{
foreach (int item in arr)
{
}
}
}
结果如下:
结论
在上面的例子中,我们可以看到for循环比foreach循环略快。我们还可以看到两者使用相同的内存分配。
我认为使用Parallel.ForEach()以及ConcurrentDictionary或ConcurrentBag会更快
下面是Parallel.ForEach()的例子
var primeNumbers = new ConcurrentBag<T>();
Parallel.ForEach(numbers, number =>
{
if (IsPrime(number))
{
primeNumbers.Add(number);
}
});
And
var productImage = new ConcurrentDictionary<int,ResultModel>();
Parallel.ForEach(pendingActiveImagesBatch, pictureItem =>
{
productImage.TryAdd(pictureItem.Id,pictureItem));
});
引用平行。ForEach
参考ConcurrentDictionary最后