我如何在Java中编写正确的微基准testing?

你如何在Java中编写(并运行)一个正确的微基准testing?

我在这里寻找代码示例和评论,说明各种要考虑的事情。

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

相关: 秒表基准testing是否可以接受?

关于从Java HotSpot的创build者编写微型基准testing的技巧:

规则0:在JVM上阅读一篇有信誉的论文,并进行微观基准testing。 Brian Goetz,2005年是一个很好的例子 。 不要期望微观基准太多, 他们只测量有限的JVM性能特征。

规则1:始终包含一个预热阶段,它将一直运行testing内核,足以在定时阶段之前触发所有初始化和编译。 (预热阶段的迭代次数较less,经验法则是数万次内循环迭代)。

规则2:始终使用-XX:+PrintCompilation-verbose:gc等运行,这样您就可以在定时阶段validation编译器和JVM的其他部分没有做出意外的工作。

规则2.1:在定时和预热阶段的开始和结束处打印消息,以便在定时阶段validation规则2没有输出。

规则3:了解-client和-server与OSR和常规编译之间的区别。 -XX:+PrintCompilation标志报告带有符号的OSR编译来表示非初始入口点,例如: Trouble$1::run @ 2 (41 bytes) 。 首选服务器到客户端,并定期到OSR,如果你是最好的performance。

规则4:注意初始化效果。 因为打印加载和初始化类,所以不要在计时阶段第一次打印。 不要在加热阶段(或最终报告阶段)之外加载新的类,除非您正在专门testing类加载(在这种情况下仅加载testing类)。 规则2是您抵抗这种影响的第一道防线。

规则5:意识到去优化和重新编译的效果。 不要在定时阶段第一次采用任何代码path,因为编译器可能会垃圾并重新编译代码,这是基于之前乐观的假设,即path根本不会被使用。 规则2是您抵抗这种影响的第一道防线。

规则6:使用适当的工具来阅读编译器的思想,并期望它会产生的代码感到惊讶。 在形成关于什么使速度更快或更慢的理论之前,亲自检查代码。

规则7:减less测量中的噪音。 在一台安静的机器上运行你的基准,运行几次,丢弃exception值。 使用-Xbatch将编译器与应用程序序列化,并考虑设置-XX:CICompilerCount=1以防止编译器与其自身并行运行。

规则8:使用一个库作为你的基准,因为它可能更高效,并且已经为了这个唯一目的而被debugging。 如JMH , Caliper或Bill和Paul的Java优秀UCSD基准testing 。

我知道这个问题已被标记为答案,但我想提两个库,使我们能够写微基准

来自Google的Caliper

入门教程

  1. http://codingjunkie.net/micro-benchmarking-with-caliper/
  2. http://vertexlabs.co.uk/blog/caliper

来自OpenJDK的JMH

入门教程

  1. 避免在JVM上进行基准testing
  2. http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  3. http://java-performance.info/jmh/

Java基准testing的重要内容是:

  • 在计时之前先多次运行代码来预热JIT
  • 确保你运行足够长的时间,以便能够在几秒或更好的几十秒内测量结果
  • 虽然在迭代之间不能调用System.gc() ,但在testing之间运行它是一个好主意,所以每个testing都希望得到一个“干净”的内存空间来处理。 (是的, gc()更多的是一个暗示,而不是一个保证,但它很可能是真的会收集我的经验)。
  • 我喜欢展示迭代和时间,以及可以缩放的时间/迭代分数,以使得“最佳”algorithm得分1.0,而其他得分以相对方式得分。 这意味着您可以长时间运行所有algorithm,改变迭代次数和时间,但仍可获得可比较的结果。

我只是在用.NET编写一个基准testing框架的博客。 我有几个 较早的post ,可能会给你一些想法 – 当然,并不是所有的东西都是合适的,但也有一些可能。

jmh是OpenJDK的最新成员,由Oracle的一些性能工程师编写。 当然值得一看。

jmh是一个用于构build,运行和分析用Java和其他语言编写的面向JVM的纳米/微观/macros基准的Java线程。

样品中埋藏的非常有趣的信息testing评论 。

也可以看看:

  • 避免在JVM上进行基准testing
  • 讨论jmh的主要优势 。

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

这取决于你想要testing什么。 如果您对延迟感兴趣,请使用时间/迭代,如果您对吞吐量感兴趣,请使用迭代/时间。

确保你以某种方式使用以基准代码计算的结果。 否则,您的代码可以优化。

如果您试图比较两种algorithm,则在每个algorithm上至less执行两个基准,交替sorting。 即:

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

在不同的通行证中,我发现在相同的algorithm运行时有一些明显的差异(有时候是5-10%)。

另外,确保n非常大,这样每个循环的运行时间至less在10秒左右。 迭代次数越多,基准时间内的数值越高,数据越可靠。

在Java中编写微型基准testing有许多可能的缺陷。

首先:你必须计算所有事件的花费时间或多或less是随机的:垃圾收集,caching效果(OS的文件和CPU的内存),IO等

第二:你不能相信测量时间的准确性很短的时间间隔。

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

我的build议是:让基准testing运行几秒钟,这比运行时间在毫秒级更可靠。 预热JVM(意味着至less在没有测量的情况下运行基准testing,JVM可以运行优化)。 并多次运行你的基准(可能是5次),并取中间值。 在新的JVM实例中运行每个微基准testing(调用每个基准testing的新Java),否则JVM的优化效果可能影响以后运行的testing。 不要执行那些在热身阶段没有执行的东西(因为这可能触发类加载和重新编译)。

还应该注意的是,在比较不同的实现时分析微基准的结果也可能是重要的。 因此应该进行显着性检验 。

这是因为在基准testing的大部分运行期间,实施A可能比实施B更快。 但A也可能有更高的价差,所以A的实测收益与B相比不会有什么意义。

所以正确编写和运行一个微基准testing也是很重要的,而且要正确分析它。

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

为了增加其他优秀的build议,我也要注意以下几点:

对于某些CPU(例如TurboBoost的Intel Core i5系列),温度(以及当前正在使用的内核数量,以及更高的利用率)会影响时钟速度。 由于CPUdynamic计时,这可能会影响您的结果。 例如,如果您有单线程应用程序,则最大时钟速度(使用TurboBoost)高于使用所有内核的应用程序。 因此,这可能会干扰某些系统上单线程和multithreading性能的比较。 请记住,温度和电压还会影响Turbo频率的维持时间。

也许你有一个直接控制的更重要的方面:确保你测量的是正确的东西! 例如,如果您使用System.nanoTime()来testing某个特定位的代码,请将调用分配到有意义的位置,以避免测量您不感兴趣的事物。例如,不要做:

 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");