如何用Java编写(并运行)正确的微基准测试?

我正在寻找一些代码示例和注释,以说明需要考虑的各种事情。

示例:基准测试应该测量时间/迭代还是迭代/时间,为什么?

相关:秒表基准测试是可接受的?


当前回答

在Java中编写微基准测试有许多可能的陷阱。

首先:你必须计算各种随机占用时间的事件:垃圾收集、缓存效果(OS对文件的缓存效果和CPU对内存的缓存效果)、IO等。

第二:对于非常短的时间间隔,您不能相信测量时间的准确性。

第三:JVM在执行时优化代码。因此,在同一个jvm实例中不同的运行将变得越来越快。

My recommendations: Make your benchmark run some seconds, that is more reliable than a runtime over milliseconds. Warm up the JVM (means running the benchmark at least once without measuring, that the JVM can run optimizations). And run your benchmark multiple times (maybe 5 times) and take the median-value. Run every micro-benchmark in a new JVM-instance (call for every benchmark new Java) otherwise optimization effects of the JVM can influence later running tests. Don't execute things, that aren't executed in the warmup-phase (as this could trigger class-load and recompilation).

其他回答

http://opt.sourceforge.net/ Java Micro Benchmark -确定不同平台上计算机系统的比较性能特征所需的控制任务。可用于指导优化决策和比较不同的Java实现。

如果您正在尝试比较两种算法,那么每种算法至少要进行两次基准测试,交替使用顺序。例如:

for(i=1..n)
  alg1();
for(i=1..n)
  alg2();
for(i=1..n)
  alg2();
for(i=1..n)
  alg1();

我发现了一些明显的差异(有时5-10%)在运行时相同的算法在不同的通行证。

此外,还要确保n非常大,以便每个循环的运行时间至少为10秒左右。迭代次数越多,基准测试时间中的数字就越重要,数据就越可靠。

为了补充其他优秀的建议,我还会注意以下几点:

For some CPUs (e.g. Intel Core i5 range with TurboBoost), the temperature (and number of cores currently being used, as well as thier utilisation percent) affects the clock speed. Since CPUs are dynamically clocked, this can affect your results. For example, if you have a single-threaded application, the maximum clock speed (with TurboBoost) is higher than for an application using all cores. This can therefore interfere with comparisons of single and multi-threaded performance on some systems. Bear in mind that the temperature and volatages also affect how long Turbo frequency is maintained.

也许您可以直接控制的一个更根本的重要方面是:确保您在测量正确的东西!例如,如果您正在使用System.nanoTime()对特定代码进行基准测试,请将对赋值的调用放在有意义的位置,以避免测量您不感兴趣的内容。例如,不要做:

long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");

问题是,当代码完成时,您不能立即得到结束时间。相反,试试下面的方法:

final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");

基准应该测量时间/迭代还是迭代/时间,为什么?

这取决于你要测试什么。

如果您对延迟感兴趣,则使用时间/迭代,如果您对吞吐量感兴趣,则使用迭代/时间。

jmh是最近添加到OpenJDK的,是由Oracle的一些性能工程师编写的。当然值得一看。

jmh是一个Java工具,用于构建、运行和分析用Java和其他针对JVM的语言编写的纳米/微/宏基准测试。

样本测试注释中隐藏着非常有趣的信息。

参见:

避免JVM上的基准测试陷阱 讨论jmh的主要优势。