for VS. foreach 那个性能更高,为什么,怎么选择

forforeach 的效率问题是个老问题了,从网上看到的是众说纷纭,有说for效率高的也有说foreach效率高的,还有说测试方法有问题的;鉴于此,我就自己做了个试验证明一下,然后探究一下可能的原因。

 

先看测试结果:

forvsforeach.png

我的测试结果是 for 的效率要比 foreach 高出一截来。

 

再看测试代码(如果大家觉得我的测试有问题,请提出来呀):

 

ContractedBlock.gif ExpandedBlockStart.gif forVSforeach
None.gifusing System;
None.gif
using System.Collections.Generic;
None.gif
using System.Text;
None.gif
using System.Diagnostics;
None.gif
None.gif
namespace ForVSForEach
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
class Program
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
const int ARRAY_LENGTH = 9000000;
InBlock.gif
InBlock.gif        
static void Main(string[] args)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif           ForVSForEach(
8);
InBlock.gif
InBlock.gif           ForVSForEach(
new Person());
InBlock.gif
InBlock.gif           Console.Read();
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif
InBlock.gif        
static void ForVSForEach<T>(T type) where T:new()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            T[] array 
= new T[ARRAY_LENGTH];
InBlock.gif            
for (int i = 0; i < array.Length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                array[i] 
= new T();
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            Stopwatch watch 
= new System.Diagnostics.Stopwatch();
InBlock.gif            T tempFor 
= default(T);
InBlock.gif            watch.Start();
InBlock.gif            
for (int i = 0; i < array.Length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                tempFor 
= array[i];
ExpandedSubBlockEnd.gif            }

InBlock.gif            watch.Stop();
InBlock.gif
InBlock.gif            
long forTicks = watch.ElapsedTicks;
InBlock.gif
InBlock.gif            T tempForeach 
= default(T);
InBlock.gif            watch.Reset();
InBlock.gif            watch.Start();
InBlock.gif            
foreach (T i in array)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                tempForeach 
= i;
ExpandedSubBlockEnd.gif            }

InBlock.gif            watch.Stop();
InBlock.gif            
long forEachTicks = watch.ElapsedTicks;
InBlock.gif
InBlock.gif            
//Console.WriteLine(tempFor);
InBlock.gif
            Console.WriteLine("使用的类型:{0},循环次数:{1}",typeof(T),ARRAY_LENGTH);
InBlock.gif
InBlock.gif            Console.WriteLine(
"for use time {0}", forTicks);
InBlock.gif
InBlock.gif            Console.WriteLine(
"foreach use time {0}", forEachTicks);
InBlock.gif
InBlock.gif
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
class Person
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public Person() dot.gif{ }
InBlock.gif
InBlock.gif        
private int _id;
InBlock.gif
InBlock.gif        
public int ID
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gifreturn _id; }
ExpandedSubBlockStart.gifContractedSubBlock.gif            
set dot.gif{ _id = value; }
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
private string  _name;
InBlock.gif
InBlock.gif        
public string  Name
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gifreturn _name; }
ExpandedSubBlockStart.gifContractedSubBlock.gif            
set dot.gif{ _name = value; }
ExpandedSubBlockEnd.gif        }

InBlock.gif
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif

 

原因分析:

为了使一个类实例可以被foreach 需要继承 IEnumerable 接口,而此接口只有一个方法,返回一个IEnumerator的接口实例,这就说明foreach是通过操纵IEnumerator的方法来实现的。我们再看IEnumerator的两个方法和一个属性:

属性:object Current {get;}

方法:bool MoveNext()

         void Reset()

我们可以推断一下foreach的实现,下面是伪代码:(下面的假设是错误的)

None.gif IEnumerator enumerator  =  IEnumerableInstance. GetEnumerator();
None.gifObject foreachObj 
=   null ;
None.gifenumerator.Reset(); 
// 有可能会在此处执行一下Reset以保证从集合的第一个元素开始foreach。
None.gif

None.gifforeachStart :
None.gif
if  (enumerator.MoveNext())
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif         foreachObj 
= enumerator.Current;
ExpandedSubBlockStart.gifContractedSubBlock.gif         
dot.gif{
InBlock.gif
InBlock.gif                   
//this area is the foreach block
ExpandedSubBlockEnd.gif
}

InBlock.gif
goto foreachStart;
ExpandedBlockEnd.gif}

None.gifforeachEnd:
None.gif

 

 

让我们再分析一下for(int i=0;i<array.length;i++)的可能执行过程:

None.gif int  i  =   0 ;
None.gifforStart:
None.gif
if (i <
array.length)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
{
ExpandedSubBlockStart.gifContractedSubBlock.gif         
dot.gif
{
InBlock.gif                   
// this area is the for block

ExpandedSubBlockEnd.gif
         }

InBlock.gif
goto  forStart;
ExpandedBlockEnd.gif}

None.gifforEnd:
None.gif

 

很显然for的执行过程中少了几个方法的执行,而且要少一些额外的步骤,这是不是for效率比foreach稍高一筹的原因呢?由于以上推断纯属个人推断,所以还不敢下结论,请大家帮忙找一下理论根据。

 

最后:

虽然说for比foreach效率稍微高一点 ,但是foreach的更优雅一点,另外for除了效率之外也有一个优点就是我们可以在执行for的过程中更新,删除集合的元素值,而在foreach中这是不允许的。

 

如果既可以使用for也可以使用foreach的时候,我们可以使用foreach使代码优雅一点,除非这段代码需要特别注意性能。

 

请大家注意:

以上的结论和推断都是错误的,上面的那个dos窗口图片中的执行结果,是在调试模式下面的执行结果,调试模式下没有性能优化,和实际结果相差很远,实际的编译成release版本的执行结果如下:

forvsforeach.release.png

大家可以看到经过优化之后,是

foreach 的性能更高一点。

 

理论根据IL编译代码(来自一位老外的blog http://blogs.msdn.com/kevin_ransom/archive/2004/04/19/116072.aspx ):

    static void One()
    {
        int[] a = new int[10000];
        foreach(int i in a)
            Console.WriteLine(i);
    }

  static void Two()
    {
        int[] a = new int[10000];
        for(int j=0; j<a.Length; j++)
       {
            int i = a[j];
            Console.WriteLine(i);
        }
    }

.method private hidebysig static void One() cil managed
{
// Code size 38 (0x26)
.maxstack 2
.locals init (int32[] V_0,
int32 V_1,
int32[] V_2,
int32 V_3)

.method private hidebysig static void Two() cil managed
{
// Code size 36 (0x24)
.maxstack 2
.locals init (int32[] V_0,
int32 V_1,
int32 V_2)

IL_0000: ldc.i4 0x2710
IL_0005: newarr [mscorlib]System.Int32
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: stloc.2
IL_000d: ldc.i4.0
IL_000e: stloc.3
IL_000f: br.s IL_001f

IL_0000: ldc.i4 0x2710
IL_0005: newarr [mscorlib]System.Int32
IL_000a: stloc.0
IL_000b: ldc.i4.0
IL_000c: stloc.1
IL_000d: br.s IL_001d


IL_0011: ldloc.2
IL_0012: ldloc.3
IL_0013: ldelem.i4
IL_0014: stloc.1
IL_0015: ldloc.1
IL_0016: call void [mscorlib]System.Console::WriteLine(int32)
IL_001b: ldloc.3
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.3
IL_001f: ldloc.3
IL_0020: ldloc.2
IL_0021: ldlen
IL_0022: conv.i4
IL_0023: blt.s IL_0011

IL_000f: ldloc.0
IL_0010: ldloc.1
IL_0011: ldelem.i4
IL_0012: stloc.2
IL_0013: ldloc.2
IL_0014: call void [mscorlib]System.Console::WriteLine(int32)
IL_0019: ldloc.1
IL_001a: ldc.i4.1
IL_001b: add
IL_001c: stloc.1
IL_001d: ldloc.1
IL_001e: ldloc.0
IL_001f: ldlen
IL_0020: conv.i4
IL_0021: blt.s IL_000f

IL_0025: ret
} // end of method MyClass::One


IL_0023: ret
} // end of method MyClass::Two

上面的表格是 for foreach 编译出来的 IL 代码,蓝色部分是循环的部分,执行步骤完全相同,所以理论上应该 for foreach 的性能应该是一样的。

大家可以参考下面的三篇文章:
http://blogs.msdn.com/kevin_ransom/archive/2004/04/19/116072.aspx

http://blogs.msdn.com/brada/archive/2004/04/29/123105.aspx

http://www.cnblogs.com/WuCountry/archive/2007/02/27/658710.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值