http://www.bmestu.com/studynewstail.asp?Stnew_ID=163
昨天去网吧通宵,闲着无聊,翻着博客园,发现了一篇文章,《深入了解foreach和for》,里面用汇编代码详细地剖析了for和foreach的效率问题。
今天我也做了一些测试,加上一些自己从前的想法,写一下这个文章。来解释一下for和foreach的效率问题。当然,里面是个人见解,如果各位有其他观点,希望各位不吝赐教。
我一共编写了四套代码,每套代码都是通过启动调试和不启动调试两种情况分别运行。得到的结果很出乎我的意料,也就是说每一种情况下,得到的结论是不尽相同的,于是,我才明白为什么网上这么多关于for和foreach的文章,得到的结果却是千奇百怪。
首先,我先来写一下我的测试代码,然后再来谈谈我对这个问题的看法。
Code
1 static void Main(string[] args)
2 {
3 ForTest();
4 ForeachTest();
5 Console.Read();
6 }
7 static void ForTest()
8 {
9 const long length = 40000000;
10 Stopwatch thisStopwatch = new Stopwatch();
11
12 long[] i = new long[length];
13 for (long j = 0; j < length; j++)
14 {
15 i[j] = j;
16 }
17 thisStopwatch.Start();
18 for (long j = 0; j < i.Length; j++)
19 {
20 long k = i[j];
21 //i[j] = 1;
22 //string p = string.Empty;
23 //string p = "111";
24 }
25 thisStopwatch.Stop();
26 Console.WriteLine(thisStopwatch.Elapsed.Milliseconds);
27 }
28 static void ForeachTest()
29 {
30 const long length = 40000000;
31 Stopwatch thisStopwatch1 = new Stopwatch();
32 long[] i = new long[length];
33 for (long j = 0; j < length; j++)
34 {
35 i[j] = j;
36 }
37 thisStopwatch1.Start();
38 foreach (long j in i)
39 {
40 //i[j] = 1;
41 //string p = string.Empty;
42 //string p = "111";
43 }
44 thisStopwatch1.Stop();
45
46 Console.WriteLine(thisStopwatch1.Elapsed.Milliseconds);
47 }
1 static void Main(string[] args)
2 {
3 ForTest();
4 ForeachTest();
5 Console.Read();
6 }
7 static void ForTest()
8 {
9 const long length = 40000000;
10 Stopwatch thisStopwatch = new Stopwatch();
11
12 long[] i = new long[length];
13 for (long j = 0; j < length; j++)
14 {
15 i[j] = j;
16 }
17 thisStopwatch.Start();
18 for (long j = 0; j < i.Length; j++)
19 {
20 long k = i[j];
21 //i[j] = 1;
22 //string p = string.Empty;
23 //string p = "111";
24 }
25 thisStopwatch.Stop();
26 Console.WriteLine(thisStopwatch.Elapsed.Milliseconds);
27 }
28 static void ForeachTest()
29 {
30 const long length = 40000000;
31 Stopwatch thisStopwatch1 = new Stopwatch();
32 long[] i = new long[length];
33 for (long j = 0; j < length; j++)
34 {
35 i[j] = j;
36 }
37 thisStopwatch1.Start();
38 foreach (long j in i)
39 {
40 //i[j] = 1;
41 //string p = string.Empty;
42 //string p = "111";
43 }
44 thisStopwatch1.Stop();
45
46 Console.WriteLine(thisStopwatch1.Elapsed.Milliseconds);
47 }
首先来解释一下,我认为这段代码是比较有代表性的。然后请大家注意下我循环语句中的注释,我四套代码的意思是每次循环体内为空,或者每次打开一句注释。由于在下不会再文章里插入图片,所以我来大致下一下我测试的结果,结果是多次测试后取得平均值。
|
For的调试 |
For的不调试 |
Foreach的调试 |
Foreach的不调试 |
空 |
310 |
268 |
263 |
262 |
赋值操作 |
416 |
429 |
467 |
387 |
String p=string.empty |
339 |
296 |
266 |
282 |
String p=”111” |
329 |
292 |
279 |
284 |
当然,这些数据是随着运行环境和时间的不同,会有这比较明显的误差。但是我想基本可以说明一定问题,也就是说不同的循环体,就会造就不同的结果。
好了,我们抛开那些数据不谈,先来说说这个原理。我们知道foreach是迭代器模式的一种高级语言实现,也就是说foreach实际上也是由for,do--while等语句所形成的,我们知道C#语言的编译过程是首先通过CLR将其编译成IL代码,然后再通过JIT编译器来编译执行。因此,在IL代码上,其实得到的代码是一样的。因此,我认为关于for语句和foreach语句的效率问题,取决的不是他们语言的本质,而是取决于程序员本身对自己程序的设计与编译器的优化孰优孰劣的问题。
另外,我想指出的是,关于网上一些对for和foreach语句的效率差别,我想可能是是在编译的环境下造成的,因为允许编译的条件下,编译器并未对代码做任何优化,所以是没有说服力的。
我来举这样一个例子,我们来看两个for循环语句:
<1> int[] array = new int[6];
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(i);
}
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(i);
}
<2> int[] array = new int[6];
int length = array.Length;
for (int i = 0; i < length; i++)
{
Console.WriteLine(i);
}
int length = array.Length;
for (int i = 0; i < length; i++)
{
Console.WriteLine(i);
}
可能很多人会认为程序二的效率会高于程序一的效率,可是结论却是恰恰相反的。尤其在海量数据的情况下,程序二的效率是远远高于程序一的。这个问题我们要了解,在.net中,.net代码都是托管的安全代码,程序的每一块内存都是要保证安全的,也就是说,每一段内存都是在被监视着的。程序二的做法实际是把一段内存分成了两块。也就是说整个for语句应该是一个段,我先来写一下程序二相类似的代码,然后我们来做对比。
<3> int[] array=new int[6];
int length=array.Length;
for(int i=0;i<length;i++)
{
if(i<array.Length)
Console.WriteLine(i);
else
throw new Exception();
}
我们来对比程序一和程序三,在程序一中,程序做了6次求Length的操作,但是在程序三中,程序不仅依然做了6次求Length的操作,还做了6次比较操作。因此,哪个效率高哪个效率低就很明显了。
我举这个例子的意思就是说,在本质上,for和foreach在效率上是没有差别的。但是,对于很多SuperProgrammer来说,他们对程序的理解一定是高于编译器的,因此对于他们来说,for语句的效率是高于foreach的。但是,我想,对于大多人来说,没有谁敢保证自己是一定强于编译器的。
因此,从效率上来讲,究竟是选择for还是foreach,我们真正该思考的不是哪个效率更高,而是应该考虑,在编译器和你自己的代码能力两者之间,你究竟更相信哪个。
当然,相比于for语句来说,尤其是对一些集合,链表等来讲,foreach的代码优雅度都是高于for语句的。
因此,在选择上,在<<Effective
C#>>中有这样一条,选择foreach循环,我想这对于大多数程序员都是实用的。提高了可读性,减少了代码量,同时也可以避免你自己在代码设计上的失误,何乐而不为呢?