增强的循环性能比传统的索引查找更差?

我刚刚看到这个看似无害的评论 ,对ArrayList和原始String数组进行基准测试。 它来自几年前,但OP写道

我注意到使用String s:stringsList比使用旧式for循环访问列表慢约50%。 去搞清楚…

没有人在原帖中评论它,测试看起来有点可疑(太短不准确),但是当我读到它时,我几乎从椅子上掉了下来。 我从来没有对“传统”的循环进行基准测试,但是我正在开发一个使用增强循环对ArrayList实例进行数亿次迭代的项目,所以这是我关注的问题。

我将在这里做一些基准测试并发布我的发现,但这显然是我的一个大问题。 我可以在网上找到关于相对性能的宝贵的小信息,除了一些随便提到的ArrayLists的增强循环在Android下运行速度慢得多 。

有没有人经历过这个? 这种性能差距是否仍然存在? 我会在这里发表我的发现,但读到它时非常惊讶。 我怀疑如果这个性能差距确实存在,它已经在更现代的VM中得到修复,但我想我现在必须做一些测试并确认。

更新:我对我的代码进行了一些更改,但已经怀疑其他人已经指出的内容:确定增强的for循环速度较慢,但​​在非常简单的紧密循环之外,成本应该是成本的一​​小部分。循环的逻辑。 在我的情况下,即使我使用增强型循环迭代非常大的字符串列表,我在循环中的逻辑也足够复杂,甚至在切换到基于索引的循环之后我甚至无法测量差异。

TL; DR:增强循环确实比传统的基于索引的循环更慢; 但对于大多数应用来说,差异应该可以忽略不计。

您遇到的问题是使用Iterator比使用直接查找要慢。 在我的机器上,每次迭代的差异大约为0.13 ns。 使用数组代替每次迭代节省大约0.15 ns。 在99%的情况下,这应该是微不足道的。

public static void main(String... args) { int testLength = 100 * 1000 * 1000; String[] stringArray = new String[testLength]; Arrays.fill(stringArray, "a"); List stringList = new ArrayList(Arrays.asList(stringArray)); { long start = System.nanoTime(); long total = 0; for (String str : stringArray) { total += str.length(); } System.out.printf("The for each Array loop time was %.2f ns total=%d%n", (double) (System.nanoTime() - start) / testLength, total); } { long start = System.nanoTime(); long total = 0; for (int i = 0, stringListSize = stringList.size(); i < stringListSize; i++) { String str = stringList.get(i); total += str.length(); } System.out.printf("The for/get List loop time was %.2f ns total=%d%n", (double) (System.nanoTime() - start) / testLength, total); } { long start = System.nanoTime(); long total = 0; for (String str : stringList) { total += str.length(); } System.out.printf("The for each List loop time was %.2f ns total=%d%n", (double) (System.nanoTime() - start) / testLength, total); } } 

当以10亿个条目运行条目打印时(使用Java 6更新26)

 The for each Array loop time was 0.76 ns total=1000000000 The for/get List loop time was 0.91 ns total=1000000000 The for each List loop time was 1.04 ns total=1000000000 

当运行10亿个条目时打印(使用OpenJDK 7)

 The for each Array loop time was 0.76 ns total=1000000000 The for/get List loop time was 0.91 ns total=1000000000 The for each List loop time was 1.04 ns total=1000000000 

即完全相同。 ;)

每个声称X在JVM上比Y慢,并没有解决本文中提出的所有问题,而第二部分则是对典型JVM性能的恐惧和谎言。 这适用于原始问题所引用的评论以及GravityBringer的答案。 我很抱歉这么粗鲁,但除非你使用适当的微基准测试技术,否则你的基准测试会产生非常严重偏斜的随机数。

如果您对更多解释感兴趣,请告诉我。 虽然这些都在我提到的文章中。

GravityBringer的数字似乎不对,因为我知道ArrayList.get()与VM优化后的原始数组访问速度一样快。

我在我的机器-server模式下运行了两次GravityBringer测试

 50574847 43872295 30494292 30787885 (2nd round) 33865894 32939945 33362063 33165376 

这种测试的瓶颈实际上是内存读/写。 从数字来看,整个2个arrays都在我的L2缓存中。 如果我们减小大小以适应L1缓存,或者如果我们将大小增加到L2缓存之外,我们将看到10倍的吞吐量差异。

ArrayList的迭代器使用单个int计数器。 即使VM没有将它放入寄存器(循环体太复杂),至少它将在L1缓存中,因此r / w基本上是免费的。

最终的答案当然是在您的特定环境中测试您的特定程序。

虽然每当提出基准问题时玩不可知论者都没有帮助。

ArrayLists的情况变得更糟。 在运行Java 6.26的计算机上,存在四倍的差异。 有趣的是(并且可能非常逻辑地),原始数组没有区别。 我运行了以下测试:

  int testSize = 5000000; ArrayList list = new ArrayList(); Double[] arr = new Double[testSize]; //set up the data - make sure data doesn't have patterns //or anything compiler could somehow optimize for (int i=0;i 

循环中的算法是防止JIT编译器可能优化掉一些代码。 算术对性能的影响很小,因为运行时由ArrayList访问控制。

运行时间(以纳秒为单位):

ArrayList foreach:248,351,782

ArrayList get():60,657,907

arraysforeach:27,381,576

数组直接索引:27,468,091