JMH:在所有基准测试中使用相同的静态对象
JMH: Using the same static object in all Benchmark tests
我有一个 class 可以构建一些复杂的数据(想象一个大的 XML 或 JSON 结构 - 那种东西)。建造它需要时间。所以我想构造一次,然后在所有测试中使用相同的数据。目前我基本上在定义 main
的 class 中定义了一个 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);
}
}
这当然很糟糕......一个同样邪恶的选择是创建一个单独的 class 只是为了它可以容纳一个静态对象。使用像
这样的东西会很好
Options opt = new OptionsBuilder()
...
.param(/*my Data object comes here*/)
但 param
只接受字符串,所以不确定如何将对象(更重要的是:对象的同一实例!)传递给它。
那么有什么比我上面描述的全局对象更优雅的吗?
我只想将您所有的基准测试方法移动到一个 class 中,将您的状态对象定义为内部 class,然后将状态注入到每个方法中:
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);
}
}
请注意,您的所有基准测试方法都应 return 值,否则编译器可能会将其作为死代码消除。
遗憾的是,JMH 无法在基准测试之间共享数据。
一方面,这打破了基准测试隔离,当一个基准测试可以静默修改另一个基准测试的输入数据,导致比较不正确。这就是为什么您需要 @Setup
每个基准测试的 @State
对象。
但更重要的是,当 JMH 将执行每个测试时,无论您构建什么技巧来在基准测试之间共享数据(例如,可从两者访问的 static
字段)都会在默认的 "forked" 模式下中断在自己的虚拟机中。值得注意的是,你用 static final Data TestSet.PARSE_ME
建议的事情实际上会为每个 @Benchmark
执行,因为每个新的 VM 实例无论如何都必须初始化 TestSet
;)当然,你可以禁用分叉,但是引入的问题多于解决的问题。
因此,花时间使设置成本更容易接受可能是一个更好的主意,这样它就不会非常痛苦。例如,反序列化磁盘中的数据而不是计算它。或者,想出一种更快的计算方法。
我有一个 class 可以构建一些复杂的数据(想象一个大的 XML 或 JSON 结构 - 那种东西)。建造它需要时间。所以我想构造一次,然后在所有测试中使用相同的数据。目前我基本上在定义 main
的 class 中定义了一个 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);
}
}
这当然很糟糕......一个同样邪恶的选择是创建一个单独的 class 只是为了它可以容纳一个静态对象。使用像
这样的东西会很好Options opt = new OptionsBuilder()
...
.param(/*my Data object comes here*/)
但 param
只接受字符串,所以不确定如何将对象(更重要的是:对象的同一实例!)传递给它。
那么有什么比我上面描述的全局对象更优雅的吗?
我只想将您所有的基准测试方法移动到一个 class 中,将您的状态对象定义为内部 class,然后将状态注入到每个方法中:
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);
}
}
请注意,您的所有基准测试方法都应 return 值,否则编译器可能会将其作为死代码消除。
遗憾的是,JMH 无法在基准测试之间共享数据。
一方面,这打破了基准测试隔离,当一个基准测试可以静默修改另一个基准测试的输入数据,导致比较不正确。这就是为什么您需要 @Setup
每个基准测试的 @State
对象。
但更重要的是,当 JMH 将执行每个测试时,无论您构建什么技巧来在基准测试之间共享数据(例如,可从两者访问的 static
字段)都会在默认的 "forked" 模式下中断在自己的虚拟机中。值得注意的是,你用 static final Data TestSet.PARSE_ME
建议的事情实际上会为每个 @Benchmark
执行,因为每个新的 VM 实例无论如何都必须初始化 TestSet
;)当然,你可以禁用分叉,但是引入的问题多于解决的问题。
因此,花时间使设置成本更容易接受可能是一个更好的主意,这样它就不会非常痛苦。例如,反序列化磁盘中的数据而不是计算它。或者,想出一种更快的计算方法。