我刚遇到this seemingly innocuous comment,对ArrayList和原始String数组进行基准测试。它来自几年前,但OP写道
我注意到使用String s:stringsList比使用旧式for循环访问列表慢约50%。去图......
没有人在原帖中对此进行评论,测试似乎有点可疑(太短不准确),但是当我读到它时,我几乎从椅子上掉了下来。我从来没有针对“传统”循环对基于增强循环的基准进行基准测试,但我目前正在开发一个使用增强循环对ArrayList实例进行数亿次迭代的项目,所以这是我关心的问题。
我将做一些基准测试并在此发布我的发现,但这显然是我的一个大问题。我可以在网上找到关于相对表现的宝贵的小信息,除了一些随便提到的enhanced loops for ArrayLists do run a lot slower under Android。
有没有人经历过这个?这种性能差距是否仍然存在?我会在这里发布我的发现,但读到它时非常惊讶。我怀疑如果这个性能差距确实存在,它已在更现代的虚拟机中得到修复,但我想我现在必须做一些测试并确认。
更新:我对我的代码进行了一些更改,但已经怀疑其他人已经指出的内容:确定增强的for循环速度较慢,但是在非常简单的紧密循环之外,成本应该是循环逻辑成本的微不足道的一小部分。在我的情况下,即使我使用增强型循环迭代非常大的字符串列表,我在循环中的逻辑也足够复杂,甚至在切换到基于索引的循环之后我甚至无法测量差异。
TL; DR:增强型循环确实比传统的基于索引的循环更慢;但对于大多数应用来说,差异应该可以忽略不计。
答案 0 :(得分:9)
您遇到的问题是使用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<String> stringList = new ArrayList<String>(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
即。完全相同的。 ;)
答案 1 :(得分:5)
每个声明X在JVM上比Y慢,而这个问题没有解决这个article中出现的所有问题,它的第二个part传播了对典型JVM性能的恐惧和谎言。这适用于原始问题所引用的评论以及GravityBringer的答案。我很抱歉这么粗鲁,但除非你使用适当的微基准测试技术,否则你的基准测试会产生非常严重偏斜的随机数。
如果您对更多解释感兴趣,请告诉我。虽然这些都在我提到的文章中。
答案 2 :(得分:3)
GravityBringer的数字似乎不对,因为我知道ArrayList.get()
与VM优化后的原始数组访问一样快。
我在我的机器上运行了GravityBringer测试两次,-server
模式
50574847
43872295
30494292
30787885
(2nd round)
33865894
32939945
33362063
33165376
此类测试的瓶颈实际上是内存读/写。从数字来看,整个2个阵列都在我的L2缓存中。如果我们减小大小以适应L1缓存,或者如果我们将大小增加到L2缓存之外,我们将看到10倍的吞吐量差异。
ArrayList
的迭代器使用单个int
计数器。即使VM没有将它放入寄存器(循环体太复杂),至少它将在L1缓存中,因此r / w基本上是免费的。
最终的答案当然是在您的特定环境中测试您的特定程序。
尽管在提出基准问题时玩不可知论者没有帮助。
答案 3 :(得分:-1)
ArrayLists的情况变得更糟。在运行Java 6.26的计算机上,存在四倍的差异。有趣的是(也许在逻辑上相当),原始数组没有区别。我运行了以下测试:
int testSize = 5000000;
ArrayList<Double> list = new ArrayList<Double>();
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<testSize; i++)
{
double someNumber = Math.random();
list.add(someNumber);
arr[i] = someNumber;
}
//ArrayList foreach
long time = System.nanoTime();
double total1 = 0;
for (Double k: list)
{
total1 += k;
}
System.out.println (System.nanoTime()-time);
//ArrayList get() method
time = System.nanoTime();
double total2 = 0;
for (int i=0;i<testSize;i++)
{
total2 += list.get(i);
}
System.out.println (System.nanoTime()-time);
//array foreach
time = System.nanoTime();
double total3 = 0;
for (Double k: arr)
{
total3 += k;
}
System.out.println (System.nanoTime()-time);
//array indexing
time = System.nanoTime();
double total4 = 0;
for (int i=0;i<testSize;i++)
{
total4 += arr[i];
}
System.out.println (System.nanoTime()-time);
//would be strange if different values were produced,
//but no, all these are the same, of course
System.out.println (total1);
System.out.println (total2);
System.out.println (total3);
System.out.println (total4);
循环中的算法是防止JIT编译器可能优化掉一些代码。算术对性能的影响很小,因为运行时由ArrayList访问支配。
运行时间(以纳秒为单位):
ArrayList foreach:248,351,782
ArrayList get():60,657,907
阵列foreach:27,381,576
数组直接索引:27,468,091