Java for loop 性能问题

考虑这个例子:

public static void main(final String[] args) {
    final List<String> myList = Arrays.asList("A", "B", "C", "D");
    final long start = System.currentTimeMillis();
    for (int i = 1000000; i > myList.size(); i--) {
        System.out.println("Hello");
    }
    final long stop = System.currentTimeMillis();
    System.out.println("Finish: " + (stop - start));
}

public static void main(final String[] args) {
    final List<String> myList = Arrays.asList("A", "B", "C", "D");
    final long start = System.currentTimeMillis();
    final int size = myList.size();
    for (int i = 1000000; i > size; i--) {
        System.out.println("Hello");
    }
    final long stop = System.currentTimeMillis();
    System.out.println("Finish: " + (stop - start));
}

这会造成任何差异吗?在我的机器上,第二个似乎执行得更快,但我不知道它是否真的准确。编译器会优化此代码吗?我可以认为,如果循环条件是一个不可变的对象(例如,String数组),他会这样做。


答案 1

如果你想测试这样的东西,你真的必须优化你的微基准来衡量你关心的东西。

首先,使循环便宜不可能跳过。计算总和通常可以解决问题。

其次,比较两个时间。

下面是一些同时执行这两项操作的代码:

import java.util.*;

public class Test {

public static long run1() {
  final List<String> myList = Arrays.asList("A", "B", "C", "D");
  final long start = System.nanoTime();
  int sum = 0;
  for (int i = 1000000000; i > myList.size(); i--) sum += i;
  final long stop = System.nanoTime();
  System.out.println("Finish: " + (stop - start)*1e-9 + " ns/op; sum = " + sum);
  return stop-start;
}

public static long run2() {
  final List<String> myList = Arrays.asList("A", "B", "C", "D");
  final long start = System.nanoTime();
  int sum = 0;
  int limit = myList.size();
  for (int i = 1000000000; i > limit; i--) sum += i;
  final long stop = System.nanoTime();
  System.out.println("Finish: " + (stop - start)*1e-9 + " ns/op; sum = " + sum);
  return stop-start;
}

public static void main(String[] args) {
  for (int i=0 ; i<5 ; i++) {
    long t1 = run1();
    long t2 = run2();
    System.out.println("  Speedup = " + (t1-t2)*1e-9 + " ns/op\n");
  }
}

}

如果我们运行它,在我的系统上,我们得到:

Finish: 0.481741256 ns/op; sum = -243309322
Finish: 0.40228402 ns/op; sum = -243309322
  Speedup = 0.079457236 ns/op

Finish: 0.450627151 ns/op; sum = -243309322
Finish: 0.43534661700000005 ns/op; sum = -243309322
  Speedup = 0.015280534 ns/op

Finish: 0.47738474700000005 ns/op; sum = -243309322
Finish: 0.403698331 ns/op; sum = -243309322
  Speedup = 0.073686416 ns/op

Finish: 0.47729349600000004 ns/op; sum = -243309322
Finish: 0.405540508 ns/op; sum = -243309322
  Speedup = 0.071752988 ns/op

Finish: 0.478979617 ns/op; sum = -243309322
Finish: 0.36067492700000003 ns/op; sum = -243309322
  Speedup = 0.11830469 ns/op

这意味着方法调用的开销约为 0.1 ns。如果你的循环做了不超过1-2 ns的事情,那么你应该关心这一点。否则,不要。


答案 2

我曾经做过一个项目,我的第一个任务是跟踪一些疯狂的慢代码(它是在一台全新的486机器上,大约需要20分钟才能执行):

for(size_t i = 0; i < strlen(data); i++)
{
    // do something with data[i]
}

解决方案是(将其减少到大约两分钟或更短的时间):

size_t length = strlen(data);

for(int i = 0; i < length; i++)
{
    // do something with data[i]
}

问题是“数据”超过100万个字符,strlen必须一直计算每个字符。

对于Java,“size()”方法可能返回一个变量,因此,VM将内联它。在像Android上的VM上这样的VM上,它可能不是。所以答案是“视情况而定”。

我个人的偏好是,如果一个方法每次都返回相同的结果,则永远不要多次调用该方法。这样,如果该方法确实涉及计算,则仅执行一次,然后就永远不会成为问题。


推荐