for循环和for-each循环之间是否有性能差异?
如果有的话,以下两个循环之间的性能差异是什么?
for (Object o: objectArrayList) { o.DoSomething(); }
和
for (int i=0; i<objectArrayList.size(); i++) { objectArrayList.get(i).DoSomething(); }
来自Joshua Bloch的Effective Java中的第46项:
在版本1.5中引入的for-each循环通过完全隐藏迭代器或索引variables,摆脱了混乱和错误的可能性。 由此产生的习惯用法同样适用于集合和数组:
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
当看到冒号(:)时,将其读为“in”。因此,上面的循环读取为“对于元素中的每个元素e”。请注意,使用for-each循环没有性能损失,即使对于数组。 实际上,在某些情况下,它可能会比普通的for循环提供轻微的性能优势,因为它只计算一次数组索引的极限。 虽然你可以手工做(项目45),程序员并不总是这样做。
所有这些循环都完全一样,我只是想在投入我的两分钱之前展示这些。
首先,循环List的经典方式:
for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
其次,首选的方式,因为它不容易出错(你做了多less次“哎呀,混合循环内的这些循环中的variables我和j”)?
for(String s : strings) { /* do something using s */ }
第三,微循环优化:
int size = strings.size(); for(int i=0;++i<=size;) { /* do something using strings.get(i) */ }
现在实际上是两分钱:至less当我testing这些时,第三个是以毫秒计算每个types的循环需要多长时间的最快时间,其中重复了几百万次的简单操作 – 这是使用Java 5与Windows jre1.6u10万一有人感兴趣。
虽然至less看起来是第三个是最快的,但是你真的应该问自己,是否想要在循环代码的任何地方实现这个窥孔优化的风险,因为从我所见到的情况来看,实际的循环并不是“通常是任何真正的程序中最耗时的部分(或者我只是在错误的领域工作,谁知道)。 而且就像我在Java for-each循环的前提中提到的(有些人把它称为Iterator循环 ,其他人称为for-in循环 ),使用它时不太可能碰到这个特殊的愚蠢的错误。 在讨论甚至可以比其他的更快之前,请记住,javac根本不会优化字节码(好吧,几乎总是),它只是编译它。
如果你正在进行微型优化,或者你的软件使用了很多recursion循环,那么你可能会对第三种循环types感兴趣。 只要记住在改变for循环之前和之后,你都必须对你的软件进行基准testing。
for-each循环通常应该是首选的。 如果您使用的List实现不支持随机访问,则“get”方法可能会变慢。 例如,如果使用LinkedList,则会产生遍历成本,而for-each方法则使用迭代器来跟踪其在列表中的位置。 有关每个循环的细微差别的更多信息。
那么,绩效影响是微不足道的,但不是零。 如果你看看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循环不是最快的。
使用迭代器代替索引总是更好的。 这是因为迭代器最有可能被优化为List实现,而索引(调用get)可能不会。 例如,LinkedList是一个List,但是通过它的元素进行索引比使用迭代器迭代要慢。
foreach使得你的代码的意图更加清晰,而且通常是比较小的速度改进 – 如果有的话。
每当我看到一个索引循环,我不得不parsing一下,以确保它做我认为它做的事情。例如,它是从零开始,是否包括或排除终点等?
我的大部分时间似乎花费在阅读代码(我写的或其他人写的),清晰度几乎总是比性能更重要。 现在很容易解散性能,因为Hotspot做了这么好的工作。
以下代码:
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 update 6运行Ubuntu 12.10 alpha。
一般来说,HotSpot优化了大量的间接操作和简单的还原操作,所以一般来说,除非有很多顺序或嵌套严格,否则不应该担心。
另一方面,LinkedList上的索引获取比在LinkedList上迭代器上调用下一个要慢得多,所以当你使用迭代器(在for-each循环中显式或隐式地)时,你可以避免性能问题,同时保持可读性。
不幸的是似乎有一个区别。
如果您查看两种循环的生成的字节代码,它们是不同的。
这里是一个来自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
这个Oracle有什么问题?
我已经在Windows 7上用Java 7和8试了这个。
即使像ArrayList或Vector这样的“get”是一个简单的数组查找,第二个循环仍然有额外的开销,第一个没有。 我预计它会比第一个慢一点点。
唯一可以肯定的方法就是对它进行基准testing,即使这样也不像听起来那么简单 。 JIT编译器可以为你的代码做非常意想不到的事情。
以下简要分析一下Android开发团队提出的差异:
https://www.youtube.com/watch?v=MZOf3pOAM6A
结果是存在差异,而在非常大的列表非常大的限制环境中,这可能是一个明显的差异。 在他们的testing中,每个循环花了两倍的时间。 然而,他们的testing是在一个40万整数的数组列表上进行的。 数组中每个元素的实际差异是6 微秒 。 我没有testing,他们没有说,但我希望使用对象,而不是基元的差异稍大,但即使如此,除非你正在build立库代码,你不知道你会问什么规模迭代,我认为差异不值得强调。
通过variables名objectArrayList
,我假设这是一个java.util.ArrayList
的实例。 在这种情况下,性能差异将是不明显的。
另一方面,如果它是java.util.LinkedList
的一个实例,第二种方法会比List#get(int)
是一个O(n)操作要慢得多。
所以第一种方法总是首选的,除非循环中的逻辑需要索引。
1. for(Object o: objectArrayList){ o.DoSomthing(); } and 2. for(int i=0; i<objectArrayList.size(); i++){ objectArrayList.get(i).DoSomthing(); }
两者都是相同的,但是为了简单和安全的编程使用,每种方法都有可能在第二种使用方式中出现错误。
奇怪的是,没有人提到这个问题 – foreach分配内存(以迭代器的forms),而普通的for循环不分配任何内存。 对于Android上的游戏,这是一个问题,因为这意味着垃圾收集器将定期运行。 在游戏中,你不希望垃圾收集器运行… EVER。 所以不要在绘图(或渲染)方法中使用foreach循环。
接受的答案回答了这个问题,除了ArrayList的例外情况…
由于大多数开发人员依赖ArrayList(至less我相信如此)
所以我有义务在这里添加正确的答案。
直接从开发者文档: –
增强型for循环(有时也称为“for-each”循环)可用于实现Iterable接口和数组的集合。 通过集合,分配一个迭代器来调用hasNext()和next()。 使用ArrayList,手写计数循环的速度要快3倍(有或没有JIT),但是对于其他集合,增强的for循环语法将完全等同于显式迭代器的使用。
通过数组迭代有几种select:
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; } }
零()是最慢的,因为JIT还不能优化每循环迭代一次获得数组长度的代价。
一()更快。 它将所有东西都拉到局部variables中,避免查找。 只有arrays长度才能提供性能优势。
对于没有JIT的设备来说,two()是最快的,对于带有JIT的设备来说,两者是无法区分的。 它使用Java编程语言1.5版中引入的增强for循环语法。
因此,默认情况下应该使用增强的for循环,但考虑一个用于性能关键的ArrayList迭代的手写计数循环。