【问题标题】:Surprising Java performance令人惊讶的 Java 性能
【发布时间】:2015-09-22 19:57:24
【问题描述】:

我有一个这样的 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;
  }
}

main 方法看起来像这样:

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 只是循环和 if。不涉及任何国家。最后一个具有创建查找列表的构造函数,但也只是一个简单的循环。如果我从构造函数中取出代码并使用数组文字创建查找列表,则时间是相同的。

任何想法为什么会发生这种情况?

【问题讨论】:

  • 您是否尝试过将结果累积到volatile 字段中而不是将其丢弃?
  • 你也忘了给我们看相关代码...
  • 我尝试将结果累积到volatile 字段中,但没有任何区别。我是 Java 新手,但 volatile 与缓存自己的值版本的线程有关,对吧?这个程序是纯单线程的。
  • @carlsb3rg 没错,volatile 与多线程有关,防止对字段的读/写重新排序(粗略地说)。但作为上述的副作用,它也阻止了读/写被完全优化。这就是为什么即使在单线程微基准测试中也使用它们来累积结果的原因。
  • @biziclop 积累是关键,但我不必积累到易变的领域。 @peter-lawrey 明白了,但还是谢谢你。我相信volatile 稍后会派上用场;)

标签: java performance-testing


【解决方案1】:

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

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

【讨论】:

  • @JohnBollinger 虽然最好的办法是修复测试,但我认为值得解释他得到的结果。 ;)
  • 是的。静态版本(特别是 isPrime3())的运行速度快得令人难以置信,因为它包含的所有 100 以下的素数都是 return (n == 2 || n == 3...)。当我查看数字时,JIT 内联两种方法非常有意义。
  • 现在我只需要弄清楚为什么对于 n sqrt(n) 比返回语句快 10%
  • @carlsb3rg 内联也可能是这里的关键。 ors 的长链可能会使您的方法超出内联指令的限制,而您的短循环可能会处理得更好。我建议你看看jitwatch,它可能会派上用场。
  • 非常感谢!我去看看 jitwatch 看看能不能做一些位操作。
猜你喜欢
  • 2022-06-11
  • 2017-04-10
  • 2020-09-17
  • 2015-05-13
  • 2019-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-09
相关资源
最近更新 更多