JMH:在所有Benchmark测试中使用相同的静态对象

我有一个类构造一些复杂的数据(想象一个大的XML或JSON结构 – 那种事情)。 构建它需要时间。 所以我想构建一次,然后在所有测试中使用相同的数据。 目前我基本上在一个定义main的类中定义了一个public static对象实例,然后在测试中显式引用它(代码是一个非常简单的例子):

 public class Data { // This class constructs some complicated data } public class TestSet { public static final Data PARSE_ME = new Data(...); public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(".*ParserTest") // several tests .forks(1) .build(); new Runner(opt).run(); } } @State(Scope.Thread) public class SomeParserTest { @Setup(Level.Iteration) public void setup() { Parser parser = new Parser(TestSet.PARSE_ME); } @Benchmark public void getId() { parser.getId(123); } } 

当然这很可怕……一个同样邪恶的选择就是创建一个单独的类,以便它可以容纳一个静态对象。 使用类似的东西会很好

 Options opt = new OptionsBuilder() ... .param(/*my Data object comes here*/) 

但是param只接受字符串,所以不确定如何传递一个对象(更重要的是:对象的同一个实例!)。

那么,除了上面描述的全局对象之外,还有什么更优雅的东西吗?

不幸的是,JMH无法在基准测试之间共享数据。

首先,当一个基准测试可以默默地修改另一个基准测试的输入数据时,这会破坏基准测试隔离,从而使比较不正确。 这就是为什么要为每个基准测试@State对象的原因。

但更重要的是,无论您在基准测试之间共享数据(例如,两者都可访问的static字段),您构建的任何技巧都会在默认的“分叉”模式中中断,此时JMH将在其自己的VM中执行每个测试。 值得注意的是,您建议使用static final Data TestSet.PARSE_ME实际上会为每个@Benchmark执行,因为每个新VM实例都必须初始化TestSet ;)当然,您可以禁用分叉,但这会引入更多问题而不是它解决。

因此,最好花时间让设置成本更容易忍受,这样就不会让人痛苦不堪。 例如,从磁盘反序列化数据而不是计算它。 或者,想出一种更快的计算方法。

我只是将您的所有基准测试方法移动到一个类中,将您的状态对象定义为内部类,并将状态注入每个类:

 public class ParserBenchmarks { @State(Scope.Thread) public static class StateHolder { Parser parser = null; @Setup(Level.Iteration) public void setup() { parser = new Parser(TestSet.PARSE_ME); } public Parser getParser() { return parser; } } @Benchmark public int getId_123(StateHolder stateHolder) { return stateHolder.getParser().getId(123); } @Benchmark public int getId_456(StateHolder stateHolder) { return stateHolder.getParser().getId(456); } } 

请注意,所有基准测试方法都应该返回值,否则编译器可能会将其作为死代码消除。