令人惊讶的Java性能

时间:2015-09-22 19:57:25

标签: java performance-testing

我有一个像这样的StressTester类:

public abstract class StressTest {
  public static final int WARMUP_JIT_COMPILER = 10000;

  public interface TimedAction {
    void doAction();
  }

  public static long timeAction(int numberOfTimes, TimedAction action) {
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    for (int i = 0; i < WARMUP_JIT_COMPILER; i++) {
      action.doAction();
    }
    long currentTime = bean.getCurrentThreadCpuTime();
    for (int i = 0; i < numberOfTimes; i++) {
      action.doAction();
    }
    return (bean.getCurrentThreadCpuTime() - currentTime)/1000000;
  }
}

主要方法看起来像这样:

private static boolean isPrime1(int n) { ... }
private static boolean isPrime2(int n) { ... }
private static boolean isPrime3(int n) { ... }
private static boolean isPrime4(int n) { ... }

private static final int NUMBER_OF_RUNS = 1000000;

public static void main(String[] args) {
  long primeNumberFinderTime1 = StressTest.timeAction(NUMBER_OF_RUNS, () -> {
    for (int i = 0; i < 100; i++) {
      isPrime1(i);
    }
  });
  long primeNumberFinderTime2 = StressTest.timeAction(NUMBER_OF_RUNS, () -> {
    for (int i = 0; i < 100; i++) {
      isPrime2(i);
    }
  });
  long primeNumberFinderTime3 = StressTest.timeAction(NUMBER_OF_RUNS, () -> {
    for (int i = 0; i < 100; i++) {
      isPrime3(i);
    }
  });
  long primeNumberFinderTime4 = StressTest.timeAction(NUMBER_OF_RUNS, () -> {
    for (int i = 0; i < 100; i++) {
      isPrime4(i);
    }
  });
}

当我设置它时,结果几乎与预期一致,我可以按预期交换测试和结果交换。 isPrime3isPrime1快约200倍。

我的真实代码有点复杂。我有几个类可以找到这样的素数:

class PrimeNumberFinder1 {
  @Override
  bool isPrime(i) { /* same code as in static isPrime1() */ };
}

class PrimeNumberFinder2 extends PrimeNumberFinder1 {
  @Override
  bool isPrime(i) { /* same code as in static isPrime2() */ };
}

class PrimeNumberFinder3 extends PrimeNumberFinder1 {
  @Override
  bool isPrime(i) { /* same code as in static isPrime3() */ };
}

class PrimeNumberFinder4 extends PrimeNumberFinder1 {
  @Override
  bool isPrime(i) { /* same code as in static isPrime4() */ };
}

我有一个这样的课:

class SomeClassWithPrimeNumberFinder {
  PrimeNumberFinder1 _pnf;

  void setPrimeNumberFinder(PrimeNumberFinder1 pnf) {
    _pnf = pnf;
  }

  void stressTest() {
    StressTest.doAction(10000000, () -> {
      for (int i = 0; i < 100; i++) {
        _pnf.isPrime(i);
      }
    });
  }
}

我的主要方法:

public static void main(String() args) {
  SomeClassWithPrimeNumberFinder sc = new SomeClassWithPrimeNumberFinder();
  sc.setPrimeNumberFinder(new PrimeNumberFinder1());
  sc.stressTest();
  sc.setPrimeNumberFinder(new PrimeNumberFinder2());
  sc.stressTest();
  sc.setPrimeNumberFinder(new PrimeNumberFinder3());
  sc.stressTest();
  sc.setPrimeNumberFinder(new PrimeNumberFinder4());
  sc.stressTest();
}

使用此设置PrimeNumberFind1与第一次测试中的isPrime1()一样快。但是PrimeNumberFind3在第一次测试中比isPrime3()慢大约200倍。

如果我移动PrimeNumberFind3使其先运行,我会在第一次测试中获得与isPrime3()相同的时间。剩下的时间也有点慢(5-10%),但没有像PrimeNumberFind3

前3个PrimeNumberFind只是循环和ifs。没有涉及的州。最后一个有一个构造函数,可以创建一个查找列表,但它也只是一个简单的循环。如果我从构造函数中取出代码并使用数组文字创建查找列表,那么时间是相同的。

为什么会发生这种情况?

1 个答案:

答案 0 :(得分:4)

可能发生的事情是,最初isPrime被丢弃为死代码,因为结果未被保留/使用,因此它将以不可能的速度运行,例如比时钟周期更快。

但是,当你提供多个实现时,它必须选择调用哪个方法,因此使用第二个方法需要更长的时间,但JIT不能内联两个以上的方法,因此第三个实现意味着测试是因为必须做更多工作来调用丢弃的方法,所以要慢得多。