for循环和for-each循环之间是否存在性能差异?


184

以下两个循环之间的性能差异(如果有)是什么?

for (Object o: objectArrayList) {
    o.DoSomething();
}

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}

@Keparo:这是一个“ for each”循环,而不是“ for-in”循环
Ande Turner

在Java中,它称为“ for each”,但是在目标C中,它称为“ for In”循环。
damithH 2014年


扩展的for循环性能这里讨论:stackoverflow.com/questions/12155987/...
eckes

即使会有差异,它也是如此微小,以至于您应该青睐可读性,除非这段代码每秒被执行数万亿次。然后,您将需要一个适当的基准来避免过早的优化。
Zabuzard

Answers:


212

摘自Joshua Bloch的《有效Java》第46条:

版本1.5中引入的for-each循环通过完全隐藏迭代器或index变量,消除了混乱和出错的机会。结果成语同样适用于集合和数组:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

当您看到冒号(:)时,将其读为“ in”。因此,上面的循环读为“对于元素中的每个元素e”。请注意,即使对于数组,使用for-each循环也不会降低性能。实际上,在某些情况下,它可能只比普通的for循环提供一点性能优势,因为它只计算一次数组索引的限制。尽管您可以手动执行此操作(第45项),但程序员并非总是如此。


48
值得一提的是,在for-each循环中,无法访问索引计数器(因为它不存在)
basszero 2009年

是的,但是该计数器现在在循环外部可见。当然,这是一个简单的解决方案,但是for-each也是如此!
Indolering

74
分配迭代器会降低性能。我在Android动态壁纸中有一些高度并行的代码。我看到垃圾收集器快要疯了。这是因为for-each循环在许多不同的(短期)线程中分配临时迭代器,从而导致垃圾收集器进行了大量工作。切换到基于常规索引的循环可解决此问题。
gsingh2011 2013年

1
@ gsingh2011但这也取决于您是否使用随机访问列表。我猜,对非随机访问列表使用基于索引的访问要比对随机访问列表使用for-each差很多。如果您正在使用接口List并因此不知道实际的实现类型,则可以检查列表是否是(实现)RandomAccess的实例,如果您真的很在意的话:docs.oracle.com/javase/8 /docs/api/java/util/RandomAccess.html
Puce

3
@ gsingh2011 Android文档(developer.android.com/training/articles/perf-tips.html#Loops)提到,仅在ArrayList而不是其他集合上使用foreach时,您将受到性能损失。我很好奇这是否是你的情况。
维卡里(Viccari)'17

29

所有这些循环的作用完全相同,我只想在投入两美分之前先进行演示。

首先,循环遍历List的经典方法:

for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }

第二,首选方法,因为它不易出错(您做了几次“哎呀,在循环内将这些循环中的变量i和j混合在一起”)?

for (String s : strings) { /* do something using s */ }

三,微优化的for循环:

int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }

现在是实际的2美分:至少当我测试这些时,第3个是最快的,用毫秒数计算每种类型的循环所花的时间,用一次简单的操作重复几百万次-这是使用Java 5如果有人感兴趣,请在Windows上使用jre1.6u10。

虽然至少看起来是第三个是最快的,但您确实应该问自己:是否想冒风险在循环代码中的任何地方实施这种窥视孔优化的风险,因为从我所见,实际的循环是通常,这是任何实际程序中最耗时的部分(或者,也许我只是在错误的领域工作,谁知道)。就像我在Java for-each循环的前言中提到的(有些将其称为Iterator循环,另一些称为for-in循环)一样,使用它时,您不太可能遇到一个特定的错误。在讨论甚至比其他方法更快的速度之前,请记住javac根本没有优化字节码(嗯,几乎无论如何都没有),它只是对其进行编译。

但是,如果您要进行微优化,并且/或者您的软件使用了大量的递归循环,那么您可能会对第三种循环类型感兴趣。只需记住在更改for循环之前和之后都要对软件进行基准测试,您必须使用这种奇怪的,经过微优化的循环。


5
请注意,用于与++环路I <=大小为“1型”,例如在循环内的get-方法等被调用值1,2,3
凌空

15
一种更好的方式来写微优化环为(中间体I = 0,大小= strings.size(); ++ i的<=大小;){}这是优选的,因为它的尺寸的范围的最小化
DONAL

1
第一次循环时,第三个不是从i = 1开始,而是跳过了第一个元素。并且它不需要for循环。int n = strings.length; while(n-> 0){System.out.println(“” + n +“” + strings [n]); }
Lassi Kinnunen's

1
@Dónal循环模式错过了第一个,并给出了IOOBE。这一项有效:for(int i = -1,size = list.size(); ++ i <size;)
Nathan Adams

1
“所有这些循环都完全相同”是不正确的。一种使用随机访问get(int),另一种使用Iterator。考虑一下LinkedListfor(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }由于get(int)n次执行,其性能要差得多。
史蒂夫郭

13

通常应优先使用for-each循环。如果您使用的List实现不支持随机访问,则“获取”方法可能会比较慢。例如,如果使用LinkedList,则将产生遍历成本,而for-each方法使用迭代器来跟踪其在列表中的位置。有关for-each循环细微差别的更多信息。

我认为文章现在在这里: 新位置

此处显示的链接已死。


12

嗯,对性能的影响几乎可以忽略不计,但并非为零。如果查看RandomAccess接口的JavaDoc :

根据经验,对于类的典型实例,如果实现此循环,则List实现应实现此接口:

for (int i=0, n=list.size(); i < n; i++)
    list.get(i);

比这个循环运行更快:

for (Iterator i=list.iterator(); i.hasNext();)
      i.next();

而且for-each循环正在使用带有迭代器的版本,因此ArrayList例如for-each循环并不是最快的。


真的每个吗?甚至一个数组?我在这里读到stackoverflow.com/questions/1006395/…它不涉及迭代器。
OndraŽižka2015年

@OndraŽižka:for-each循环在遍历Iterables而不是数组时使用迭代器。对于数组,使用带有索引变量的for循环。JLS中有关于此的信息。
Lii

是的,因此您首先需要使用toArray将其创建到一个数组中,但要花一些钱。
Lassi Kinnunen

6

不幸的是,似乎有所不同。

如果查看两种循环生成的字节码,它们是不同的。

这是Log4j源代码的示例。

在/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java中,我们有一个称为Log4jMarker的静态内部类,它定义了:

    /*
     * Called from add while synchronized.
     */
    private static boolean contains(final Marker parent, final Marker... localParents) {
        //noinspection ForLoopReplaceableByForEach
        for (final Marker marker : localParents) {
            if (marker == parent) {
                return true;
            }
        }
        return false;
    }

使用标准循环:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: iconst_0
       1: istore_2
       2: aload_1
       3: arraylength
       4: istore_3
       5: iload_2
       6: iload_3
       7: if_icmpge     29
      10: aload_1
      11: iload_2
      12: aaload
      13: astore        4
      15: aload         4
      17: aload_0
      18: if_acmpne     23
      21: iconst_1
      22: ireturn
      23: iinc          2, 1
      26: goto          5
      29: iconst_0
      30: ireturn

与for-each:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: aload_1
       1: astore_2
       2: aload_2
       3: arraylength
       4: istore_3
       5: iconst_0
       6: istore        4
       8: iload         4
      10: iload_3
      11: if_icmpge     34
      14: aload_2
      15: iload         4
      17: aaload
      18: astore        5
      20: aload         5
      22: aload_0
      23: if_acmpne     28
      26: iconst_1
      27: ireturn
      28: iinc          4, 1
      31: goto          8
      34: iconst_0
      35: ireturn

甲骨文怎么了?

我已经在Windows 7上使用Java 7和Java 8进行了尝试。


7
对于那些试图阅读反汇编的人来说,最终结果是循环内生成的代码是相同的,但是for-each设置似乎已经创建了一个额外的临时变量,其中包含对第二个参数的引用。如果注册了额外的隐藏变量,但是参数本身不在代码生成过程中,则for-each会更快。如果在for(;;)示例中注册了参数,则执行时间将相同。要基准?
罗宾·戴维斯

4

最好使用迭代器而不是建立索引。这是因为迭代器最有可能在List实现上被优化,而索引(调用get)则可能没有。例如,LinkedList是一个List,但是通过其元素进行索引比使用迭代器进行迭代要慢。


10
我认为性能优化中没有“总是”这样的东西。)
eckes 2015年

4

foreach使代码的意图更清晰,通常优先于速度的微小改进(如果有的话)。

每当我看到索引循环时,我都必须解析它一点点时间,以确保它执行了我认为的工作。例如,它是否从零开始,是否包含或排除了终点等?

我的大部分时间似乎都花在阅读代码上(我写的或别人写的),而清晰度几乎总是比性能更重要。如今,由于Hotspot的出色表现,它很容易让人失望。


4

如下代码:

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

interface Function<T> {
    long perform(T parameter, long x);
}

class MyArray<T> {

    T[] array;
    long x;

    public MyArray(int size, Class<T> type, long x) {
        array = (T[]) Array.newInstance(type, size);
        this.x = x;
    }

    public void forEach(Function<T> function) {
        for (T element : array) {
            x = function.perform(element, x);
        }
    }
}

class Compute {
    int factor;
    final long constant;

    public Compute(int factor, long constant) {
        this.factor = factor;
        this.constant = constant;
    }

    public long compute(long parameter, long x) {
        return x * factor + parameter + constant;
    }
}

public class Main {

    public static void main(String[] args) {
        List<Long> numbers = new ArrayList<Long>(50000000);
        for (int i = 0; i < 50000000; i++) {
            numbers.add(i * i + 5L);
        }

        long x = 234553523525L;

        long time = System.currentTimeMillis();
        for (int i = 0; i < numbers.size(); i++) {
            x += x * 7 + numbers.get(i) + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        time = System.currentTimeMillis();
        for (long i : numbers) {
            x += x * 7 + i + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        numbers = null;
        MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return x * 8 + parameter + 5L;
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
        myArray = null;
        myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return new Compute(8, 5).compute(parameter, x);
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
    }
}

在我的系统上给出以下输出:

224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895

我正在运行带有OracleJDK 1.7更新6的Ubuntu 12.10 alpha。

通常,HotSpot优化了许多间接操作和简单的冗余操作,因此,除非它们中有很多是连续的或者它们被大量嵌套,否则通常不必担心它们。

另一方面,在LinkedList上建立索引的get比在LinkedList的迭代器上调用next慢得多,因此在使用迭代器时(在for-each循环中显式或隐式),可以在保持可读性的同时避免性能下降。


3

即使使用诸如ArrayList或Vector这样的东西,其中“ get”是一个简单的数组查找,第二个循环仍然具有第一个循环所没有的额外开销。我希望它会比第一个慢一点。


第一个循环也必须获取每个元素。它在后台创建了一个迭代器来执行此操作。它们真的等效。
比尔蜥蜴

从C的角度出发,迭代器可以只增加一个指针,但是每次获取时都必须将i的值乘以指针的宽度。
Paul Tomblin,

这取决于您使用的列表类型。我认为您是对的,但是使用get永远不会更快,有时甚至会更慢。
比尔蜥蜴


3

这是对Android开发团队提出的差异的简要分析:

https://www.youtube.com/watch?v=MZOf3pOAM6A

其结果是,存在差异,并具有非常大的名单很内敛的环境中,它可能是一个明显的区别。在他们的测试中,for每个循环花费了两倍的时间。但是,他们的测试超过了400,000个整数的数组列表。数组中每个元素的实际差异为6 微秒。我还没有测试,他们也没有说,但是我希望使用对象而不是使用基元,差异会更大,但即使如此,除非您正在构建不知道将要问的内容多大的库代码进行迭代,我认为差异不值得强调。


2

通过变量名objectArrayList,我假设它是的实例java.util.ArrayList。在这种情况下,性能差异将不明显。

另一方面,如果它是的实例java.util.LinkedList,则第二种方法会比List#get(int)O(n)操作慢得多。

因此,除非循环中的逻辑需要索引,否则始终首选第一种方法。


1
1. for(Object o: objectArrayList){
    o.DoSomthing();
}
and

2. for(int i=0; i<objectArrayList.size(); i++){
    objectArrayList.get(i).DoSomthing();
}

两者的功能相同,但是为了使每种编程都容易且安全地进行编程,第二种使用方式中容易出错。


1

奇怪的是没有人提到明显的问题-foreach分配内存(以迭代器的形式),而普通的for循环不分配任何内存。对于Android上的游戏,这是一个问题,因为这意味着垃圾收集器将定期运行。在游戏中,您不希望垃圾收集器运行... EVER。因此,请勿在draw(或render)方法中使用foreach循环。


1

除了ArrayList的特殊情况外,可接受的答案还可以回答问题。

由于大多数开发人员都依赖ArrayList(我至少相信如此)

因此,我有义务在此处添加正确的答案。

直接来自开发人员文档:-

增强的for循环(有时也称为“ for-each”循环)可用于实现Iterable接口的集合以及数组。对于集合,分配了一个迭代器以对hasNext()和next()进行接口调用。使用ArrayList时,手写计数循环大约快3倍(有或没有JIT),但是对于其他集合,增强的for循环语法将完全等同于显式迭代器的用法。

遍历数组有几种选择:

static class Foo {
    int mSplat;
}

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

public void two() {
    int sum = 0;
    for (Foo a : mArray) {
        sum += a.mSplat;
    }
}

zero()最慢,因为JIT尚无法优化循环中每次迭代一次获取数组长度的成本。

one()更快。它将所有内容提取到局部变量中,从而避免了查找。只有阵列长度才能提供性能优势。

对于没有JIT的设备,two()最快,而对于具有JIT的设备,one()则无法区分。它使用Java编程语言1.5版中引入的增强的for循环语法。

因此,默认情况下应使用增强型for循环,但对于性能至关重要的ArrayList迭代,请考虑使用手写计数循环。


-2

是的,for-each变体比正常速度快index-based-for-loop

for-each变种使用iterator。因此,遍历比for基于索引的普通循环要快。
这是因为iterator针对遍历进行了优化,因为它指向的是下一个元素之前和之后的元素index-based-for-loop变慢的原因之一是,它每次都必须计算并移动到元素位置,而不是使用iterator


-3
public class FirstJavaProgram {

    public static void main(String[] args) 
    {
        int a[]={1,2,3,45,6,6};

// Method 1: this is simple way to print array 

        for(int i=0;i<a.length;i++) 
        { 
            System.out.print(a[i]+" ");
        }

// Method 2: Enhanced For loop

        for(int i:a)
        {
            System.out.print(i+" ");
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.