4

在这个问题中,我想质疑如何测试 Java 代码性能的知识。通常的方法遵循以下原则:

long start = System.nanoTime();

for( int i=0; i<SOME_VERY_LARGE_NUMBER; i++) {
    ...do something...
}

long duration = System.nanoTime() - start;
System.out.println( "Performance: " 
    + new BigDecimal( duration ).divide( 
      new BigDecimal( SOME_VERY_LARGE_NUMBER, 3, RoundingMode.HALF_UP ) ) );

“优化”版本将调用移动System.nanoTime()到循环中,增加了误差范围,因为与比较相比System.nanoTime()需要更长的时间(并且在运行时行为中更难以预测)i ++

我的批评是:

这给了我平均运行时间,但这个值取决于一个我并不真正感兴趣的因素:比如测试循环运行时的系统负载或 JIT/GC 启动时的跳转。

在大多数情况下,这种方法不是更好吗?

  1. 经常运行代码来测量以强制进行 JIT 编译
  2. 在循环中运行代码并测量执行时间。记住最小值并在该值稳定时中止循环。

我的理由是我通常想知道某些代码的速度有多快(下限)。任何代码都可能因外部事件(鼠标移动、显卡中断,因为桌面上有模拟时钟、交换、网络数据包……)而变得任意慢,但大多数时候,我只想知道有多快我的代码可以在完美的情况下。

它还可以使性能测量更快,因为我不必运行代码几秒钟或几分钟(以消除不需要的影响)。

有人可以确认/揭穿这一点吗?

4

3 回答 3

3

我认为您提出的建议非常合理,并进行了一些调整:

1)我会报告中位数 - 或一堆百分位数 - 而不是最小值。如果您的代码对垃圾收集器施加了很大压力,那么简单地取最小值很容易无法接受(只需一次迭代以适应两次连续的 GC 暂停)。

2) 在许多情况下,测量 CPU 时间而不是挂钟时间是有意义的。这解决了在同一个盒子上运行其他代码的一些影响。

3)一些基准测试工具使用两级循环:内循环重复执行操作,外循环查看内循环前后的时钟。然后在外循环的迭代中汇总观察结果。

最后,以下内容很好地概述了需要注意的特定于 JVM 的问题:如何在 Java 中编写正确的微基准测试?

于 2012-05-11T11:54:14.297 回答
2

您可以使用-XX:CompileThreshold JVM 选项来指定 JIT 何时启动。然后您可以通过在运行定时循环之前运行大于 CompileThreshold 的循环来“预热”您的测试。

于 2012-05-11T11:55:39.040 回答
1

我会运行 SOME_VERY_LARGE_NUMBER 循环 50 次并计算性能最佳循环的平均值。这通常在其他基准测试中完成,而不仅仅是代码微基准测试。

我还认为 GC 引起的性能问题通常是代码的一部分。您可能不应该将 GC 排除在外,因为分配大量内存的例程应该为此付出一定的基准价格。如果您选择的 SOME_VERY_LARGE_NUMBER 足够大,则建议的方法会考虑每次调用的平均 GC 成本。

关于您的建议:所有计时器的精度都有限,因此很可能是一个简短的例程在零时钟滴答内完成。这意味着您的算法会发现例程在零时间内运行。这显然是不正确的。

于 2012-05-11T11:39:49.813 回答