哪个循环更快?

时间:2012-03-19 16:35:38

标签: java

我有这个循环

for (it= someCollection.iterator; it.hasNext(); )
{
    //some code here
}

我把它改为:

for (it= someCollection.iterator;; )
{
    if (!it.hasNext())
        break;
    //some code here
}

第二个代码在Eclipse上的junit中的单元测试中跑得快一点。 第二个循环更快吗?我问,因为Junit给出的时间不是太精确,但它们给出了近似值

5 个答案:

答案 0 :(得分:3)

在研究这类问题时,根据块控制流程图来考虑生成的字节码是有用的,其中块是一个字节码指令序列,只能从第一条指令输入,只留在其后最后一条指令(省略退出以简化问题)。

使用此示例代码:

    for (Iterator it = c.iterator(); it.hasNext(); ) {
        System.out.println(it.next());
    }
    System.out.println("Out");

您将获得以下块控制流程图。为了便于阅读,我将等效的字节码放回源中,但System.out.println(it.next());生成的所有指令都属于一个块,因为你不能跳到中间或者离开它。

Loop Control Flow Diagram

如果您查看compiler book,则会发现it.hasNext()支配System.out.println(it.next()),因为您需要通过it.hasNext()转到System.out.println(it.next())。从System.out.println(it.next())it.hasNext()的边缘称为后边缘,因为它将节点链接到其中一个支配者。这正式定义了循环是什么。 for - 循环(Iterator it = c.iterator())中的第一个语句实际上并不属于循环。除了声明的变量的范围之外,此语句之前的while循环没有区别,但是这一点在编译后无关紧要。 第一个块(it.hasNext())是循环头。

这样的第二个例子会生成相同的图表:

    for (Iterator it = c.iterator();; ) {
        if (!it.hasNext()) {
           break;
        }
        System.out.println(it.next());
    }
    System.out.println("Out");

主要区别在于可能存在一些无用的goto语句,具体取决于编译器策略。

如果你使用javap -c查看生成的字节码这两个例子,你会得到这个(这是用javac编译的,如果使用Eclipse编译器进行编译,你可能会得到一些稍微不同的东西,因为示例):

public void loop1();
  Code:
   0:   new #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   invokevirtual   #4; //Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
   12:  astore_2
   13:  aload_2
   14:  invokeinterface #5,  1; //InterfaceMethod java/util/Iterator.hasNext:()Z
   19:  ifeq    37
   22:  getstatic   #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   25:  aload_2
   26:  invokeinterface #7,  1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
   31:  invokevirtual   #8; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   34:  goto    13
   37:  getstatic   #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   40:  ldc #9; //String Out
   42:  invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   45:  return

public void loop2();
  Code:
   0:   new #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   invokevirtual   #4; //Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
   12:  astore_2
   13:  aload_2
   14:  invokeinterface #5,  1; //InterfaceMethod java/util/Iterator.hasNext:()Z
   19:  ifne    25
   22:  goto    40
   25:  getstatic   #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   28:  aload_2
   29:  invokeinterface #7,  1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
   34:  invokevirtual   #8; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   37:  goto    13
   40:  getstatic   #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   43:  ldc #9; //String Out
   45:  invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   48:  return

唯一的区别是第一个使用ifeq 37直接到达结尾或继续下一个块(22),而另一个使用ifnegoto之后转到块{1}}(25,相当于另一个中的22)并使用goto来结束。这实际上是等效的,现代JIT编译器应该毫无困难地优化这个小差异。除此之外,你的两个循环完全相同。

我不确定你是如何进行测量的,但是你也应该意识到这不是因为System.nanoTime()给你一个纳秒的结果,它具有该顺序的分辨率,远非如此。高分辨率定时器很难实现,并且取决于硬件和操作系统。见JavaDoc

  

此方法提供纳秒精度,但不一定   纳秒分辨率(即,值的变化频率) - 否   除了分辨率至少与分辨率一样好之外,还要做出保证   currentTimeMillis()。

如果你没有得到足够高的差异,你可能不会得到与分辨率相比更重要的东西。

答案 1 :(得分:2)

我希望它们能够编译成相同的字节码。

答案 2 :(得分:0)

他们应该在同一时间执行。

有什么用?

while(it.hasNext())

答案 3 :(得分:0)

这两个例子完全相同。 注意:由于循环中没有更新语句,类似于it.next()这两个循环可能会永远运行,除非它们只有没有元素。 或者你把它们放在这里只是为了说明。

答案 4 :(得分:0)

循环语法

for(initialization; Boolean_expression; update)
{
   //Statements
}
  1. 首先执行初始化步骤,并且只执行一次。此步骤允许您声明和初始化任何循环控制变量。只要出现分号,就不需要在此处添加声明。

  2. 接下来,评估布尔表达式。如果是,则执行循环体。如果为false,则循环体不执行,控制流跳转到for循环之后的下一个语句。

  3. 执行for循环体之后,控制流会跳回到update语句。此语句允许您更新任何循环控制变量。只要在布尔表达式后面出现分号,此语句就可以留空。

  4. 现在再次评估布尔表达式。如果为真,则循环执行并且过程重复(循环体,然后更新步骤,然后是布尔表达式)。布尔表达式为false后,for循环终止。

  5. 在第二个代码中,如果条件为真,则不执行第4步。所以它比第一代码更快。