JMH项目中无法强制指定基准测试执行顺序如何解决
JMH基准测试执行顺序依赖问题解决方案
首先明确核心认知:JMH从设计层面就不承诺基准测试方法的默认执行顺序,你代码里用的@Group注解完全不是用来控制单线程下测试执行先后顺序的——这个注解的设计目的是标记多线程测试场景下,需要混合并发执行的同组测试方法,拿它做顺序控制属于用错了API,自然达不到预期效果。
现有代码的典型问题
- 注解误用:
@Group不具备顺序控制能力,同组方法会被JMH调度为混合运行,哪怕配置了单线程,也不会按方法名的先后顺序执行 - State生命周期配置不符合预期:当前
@Setup(Level.Trial)只会在整轮测试开始前初始化一个空列表,两个基准方法拿到的初始状态都是空列表,就算顺序对了,get操作也是跑在空列表上,完全不符合测试逻辑 - 参数配置冲突:Options里先后写了
.forks(0)和.forks(1),后写的配置会覆盖前者,导致前面对fork的配置全部失效
可落地的解决方式
方案1:将前置准备逻辑移入State初始化流程(符合JMH最佳实践,优先推荐)
如果你的核心测试目标是预填充完成的列表上的get操作性能,那么往列表里add元素的操作属于测试前置准备工作,不应该作为独立的基准测试存在,直接把这部分逻辑放到Setup方法里即可,这部分准备开销不会被计入后续基准测试的性能统计:
@State(Scope.Benchmark) public static class IndexedLinkedListState { public List<Integer> list; public Random random; private static final long SEED = 42L; // 固定随机种子保证测试可复现 @Setup(Level.Trial) public void setup() { list = new IndexedLinkedList<>(); random = new Random(SEED); // 预填充测试数据 profileAddFirst(list, ADD_FIRST_OPERATIONS, random); } }
如果addFirst本身也是你需要测量性能的指标,给它单独配置独立的State实例,不要和get测试共用同一个初始化状态,避免测试之间互相产生副作用。
方案2:必须保留两个独立基准且强制顺序的实现方式
JMH没有提供原生的执行顺序控制注解,如果确实需要两个基准按先后运行,可以通过在Options中按顺序传入include规则实现,JMH会按include匹配的先后顺序调度基准执行:
Options opt = new OptionsBuilder() // 先匹配addFirst基准,优先执行 .include(".*Benchmark_1A_profileAddFirstRoddeList") // 再匹配get基准,后续执行 .include(".*Benchmark_1B_profileGetRoddeList") .warmupForks(0) .warmupIterations(1) .warmupTime(TimeValue.seconds(2L)) .measurementIterations(1) .measurementTime(TimeValue.seconds(2L)) .jvmArgsPrepend("-server", "-Xms7G", "-Xmx7G") .forks(1) .timeUnit(TimeUnit.MILLISECONDS) .syncIterations(true) .threads(1) .build();
注意:该方式依赖JMH对include顺序的处理逻辑,不属于官方文档承诺的稳定行为,非必要不使用。
额外注意
不要试图依赖方法名字典序、注解排列顺序这类隐式规则控制执行顺序,JMH不同版本的内部调度逻辑可能调整,这类隐式依赖随时会失效。基准测试本身设计原则就是用例之间互相独立,不依赖其他测试的执行副作用,否则测出来的结果会被前置操作带来的JIT编译状态、GC残留、内存占用等因素干扰,数据没有参考价值。
内容的提问来源于stack exchange,提问作者coderodde
相关产品推荐
相关产品推荐

