协慌网

登录 贡献 社区

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

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

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

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

相关: 秒表基准可以接受吗?

答案

关于从 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以防止编译器与自身并行运行。尽量减少 GC 开销,设置Xmx (足够大)等于Xms并使用UseEpsilonGC如果可用)。

规则 8:使用库作为您的基准测试,因为它可能更有效,并且已经针对此唯一目的进行了调试。例如JMHCaliperBill 和 Paul 的优秀 UCSD Java 基准

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

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

我正在撰写关于. NET 中基准测试框架设计的博客。我有一对夫妇较早的帖子这或许可以给你一些想法 - 而不是一切都将是合适的,当然,但它的一些可能。