如何在Java中编写正确的微基准测试?

问题

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

我在这里寻找代码示例和注释,说明要考虑的各种事项。

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

相关阅读:Is stopwatch benchmarking acceptable?


#1 热门回答(646 赞)

关于编写微基准from the creators of Java HotSpot的提示:

**规则0:**阅读有关JVM和微基准测试的着名论文。一个好的是Brian Goetz, 2005。微观基准不要期望太多;它们仅测量有限范围的JVM性能特征。

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

**规则2:**始终使用-XX:PrintCompilation-verbose:gc等运行,因此你可以验证编译器和JVM的其他部分在计时阶段没有执行意外工作。

**规则2.1:**在计时和预热阶段的开始和结束时打印消息,因此你可以在计时阶段验证规则2中没有输出。

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

**规则4:**了解初始化效果。在打印加载和初始化类时,不要在计时阶段第一次打印。除非你专门测试类加载(并且在这种情况下仅加载测试类),否则不要在预热阶段(或最终报告阶段)之外加载新类。规则2是你抵御此类影响的第一道防线。

**规则5:**了解去优化和重新编译效果。在计时阶段第一次不要采用任何代码路径,因为编译器可能会破坏并重新编译代码,这是基于先前的乐观假设,即路径根本不会被使用。规则2是你抵御此类影响的第一道防线。

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

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

**规则8:**为你的基准测试使用库,因为它可能更高效,并且已经针对此唯一目的进行了调试。如,JMH,CaliperBill and Paul's Excellent UCSD Benchmarks for Java


#2 热门回答(206 赞)

我知道这个问题已被标记为已回答,但我想提及两个使我们能够编写微基准的库
Caliper from Google获取入门教程 - http://codingjunkie.net/micro-benchmarking-with-caliper/

  • http://vertexlabs.co.uk/blog/caliper
    JMH from OpenJDK开始学习教程 - 避免JVM上的基准测试陷阱
  • http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  • http://java-performance.info/jmh/

#3 热门回答(69 赞)

Java基准测试的重要事项是:

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

我刚刚在博客中介绍了.NET中基准测试框架的设计。我有一个couple ofearlier posts,它可能会给你一些想法 - 当然,并非一切都是合适的,但有些可能是。