for与foreach再探讨

简介:
 飞林沙 在《 for和foreach,N个人N种结论的选择 》一文中通过例子测试了 for与foreach的性能。这篇文章重点探讨下他那个测试本身。

        其实那个测试本身就是一个错误的测试。

        为什么这样说呢?让我们来仔细分析,为求简单,只分析循环体内为空的测试。

 

        原测试代码: 


复制代码

       
static   void  ForTest()
        {
            
long  length  =   40000000 ;
            Stopwatch thisStopwatch 
=   new  Stopwatch();

            
long [] i  =   new   long [length];
            
for  ( long  j  =   0 ; j  <  length; j ++ )
            {
                i[j] 
=  j;    
            }

            thisStopwatch.Start();
            
for  ( long  j  =   0 ; j  <  i.Length;  ++ j)
            {
            }

            thisStopwatch.Stop();
            Console.WriteLine(thisStopwatch.Elapsed.Milliseconds);
        }
        
        
static   void  ForeachTest()
        {
            
long  length  =   40000000 ;
            Stopwatch thisStopwatch1 
=   new  Stopwatch();
            
long [] i  =   new   long [length];
            
for  ( long  j  =   0 ; j  <  length; j ++ )
            {
                i[j] 
=  j;
            }
            thisStopwatch1.Start();
            
foreach  ( long  j  in  i)
            {
            }
            thisStopwatch1.Stop();
            
            Console.WriteLine(thisStopwatch1.Elapsed.Milliseconds);
        }
复制代码


        这个测试代码和飞林沙的原文比,略有变动。主要是去掉了length的const。
        
        我的测试结果:for -- 147    foreach -- 82
        
        直接将i.Length替换成length,测试结果:for -- 88 foreach -- 82
        
        就这两个测试而言,貌似可以得出这样一个结论:
        
        (1)只读式集合foreach比for快
        (2)在循环体中使用i.Length比使用length要慢很多
        
        实际上这两个结论都是错误的。错误的原因,在于这个测试本身就是一个不公平的测试。对for很不公平。
        
        用reflector反编译。

复制代码
        (a)
            
for  ( long  j  =   0 ; j  <  i.Length;  ++ j)
            {
            }
            
=>
            
for  ( long  j  =   0L ; j  <  numArray2.Length; j  +=   1L )
            {
            }
            
        (b)
            
for  ( long  j  =   0 ; j  <  length;  ++ j)
            {
            }
            
=>
            
for  ( long  j  =   0L ; j  <   0x2625a00L ; j  +=   1L )
            {
            }
            
        (c)
            
foreach  ( long  j  in  i)
            {
            }
            
=>
            
for  ( int  j  =   0 ; j  <  numArray2.Length; j ++ )
            {
                
long  num1  =  numArray2[j];
            }
复制代码

 

        可以看出,这里影响性能的关键是 Int32和Int64 的差别。
        
        (a)中主要是Int64的计算,还涉及到一个Int32 (numArray2.Length)到Int64的转变。
        (b)中主要是Int64的计算,不过不涉及到Int32到Int64的转变,因此比(a)快得多。
        (c)中主要是Int32的计算,其中也涉及到一个Int32到Int64的转变。至于(c)比(b)略快,这里可以看出,对于这个例子而言,编译器将foreach转变成了for循环。(c)和(b)的性能差别,其实是Int32计算+Int32->Int64计算和纯Int64计算的差别。
        
        从(c)中也可以看见编译器傻X的一面。明明j没被调用,它还要偏偏的将它转化出来。
        
        所以说,飞林沙的测试是个错误的测试。为使公平起见,需要将测试代码中的long全部改成Int32。
        
        测试结果:
        
        for中使用i.Length: for -- 22, foreach -- 52
        for中不使用i.Length: for -- 21, foreach -- 52
        
        为什么 foreach 比 for 慢这么多呢?原因上面说过,编译器太傻X了,在循环体中加了条语句:int num1 = numArray2[j];

        下面谈一谈编译器的优化问题。
        
        我们和编译器相比,各自优势是什么?
        
        我们比编译器更清楚我们的代码是干什么用的,但在代码的执行原理上比不上编译器(其实是比不上写编译器的那些人),比如,《for和foreach,N个人N种结论的选择》原文作者就没考虑到Int32和Int64的问题。当然,牛人牛到一定程度,比写编译器的人还牛,也是很有可能的。
        
        编译器比我们更清楚代码的执行原理,但是它不知道我们这行代码是干什么用的。它的优化是有前提的,第一点是不能出错误,第二点是优化的通用性。另外一点,编译器的聪明也是有限的。这几点导致编译器的优化绝大部分情况下不是最佳的优化。
        
        有很多优化策略确实可以将程序性能提高很多,但是如果不能保证程序的正确性,编译器倾向于不做这样的优化。而人工写代码,可以在代码中或者在应用中确保这种程序错误不会出现,就可以手动的选择这种优化策略。关于编译器优化,《深入理解计算机系统》一书 第5章 优化程序性能 讲的很精彩。

        所以我认为,一般情况下foreach是比for效率低一点的,只要for循环写的不是太差。
        
        针对 《for和foreach,N个人N种结论的选择》 文后的回复,我觉得有几个错误的看法需要指出。
        
        (1)所谓 for 有条件检查,foreach不要条件检查
        
        诚然,for每一促循环都要判断循环条件是否成立,难道foreach中每一次迭代都不需要判断当前迭代器的位置是否到头?
        
        (2)判断越界问题
        
        判断索引的越界,在越界时抛出异常,是容器的职责,而不是调用它的代码的职责。这一问题和for和foreach都无关系。在语义上,for与foreach并不保证不越界。这是最基本的职责分离原则,微软如果把for, foreach和越界混在一起,将是软件界的一大笑话。
        

        很简单的一个例子:

        for(int i = 1000; i<(myarray.Length)*2;i=i+2)
        {

        }

 

        编译器你去判断去吧。
        
        勿在浮沙筑高塔。 

本文转自xiaotie博客园博客,原文链接http://www.cnblogs.com/xiaotie/archive/2008/08/24/1274897.html如需转载请自行联系原作者


xiaotie 集异璧实验室(GEBLAB)

相关文章
|
26天前
|
数据处理 索引
forEach 方法有哪些替代方法?
总之,虽然`forEach`方法是一种常用的数组遍历方法,但在不同的情况下,有多种替代方法可以提供更具针对性和高效的解决方案。通过合理选择和运用这些方法,我们可以更好地处理数组数据,提高代码的质量和效率。
42 5
|
2月前
for in ,for of和forEach的区别
for in ,for of和forEach的区别
46 5
|
2月前
|
算法
|
4月前
|
索引
foreach,for in和for of的区别
foreach,for in和for of的区别
53 1
|
7月前
|
索引
for和foreach谁更快,为什么
for和foreach谁更快,为什么
282 0
|
7月前
|
存储 索引
for 和 foreach 谁更快
for 和 foreach 谁更快
117 1
|
7月前
|
JavaScript 前端开发
foreach、for in和for of的区别?
foreach、for in和for of的区别?
53 0
|
7月前
|
前端开发 JavaScript
你不知道的forEach函数
你不知道的forEach函数
|
JavaScript 前端开发 索引
foreach、for in 和for of的区别?
foreach、for in 和for of的区别?
|
索引
Array.forEach()
Array.forEach()
86 0