zoukankan      html  css  js  c++  java
  • 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)
            {

            }

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

    版权所有,欢迎转载
  • 相关阅读:
    性能优化方法
    awk命令
    jvm 性能调优工具之 jmap
    linux下tomcat 8的安装以及tomcat启动慢问题
    解析Java中的String、StringBuilder、StringBuffer类(一)
    JDBC详解系列(四)之建立Stament和执行SQL语句
    JDBC详解系列(三)之建立连接(DriverManager.getConnection)
    Paho -物联网 MQTT C Cient的实现和详解
    Paho
    Subscription wildcards(MQTT)
  • 原文地址:https://www.cnblogs.com/xiaotie/p/1274897.html
Copyright © 2011-2022 走看看