You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JMH基准测试:OperationsPerInvocation与单次add结果差异原因咨询

为什么JMH测试ArrayList.add()时单次调用和循环批量调用的百分位结果差异巨大?

你的猜测完全正确,核心原因就是JMH的框架调用开销被平摊的程度不同,再加上CPU执行效率的差异,最终导致了两个测试结果的巨大差距,具体拆解如下:

1. JMH框架开销的核心影响

ArrayList的add()方法在未触发扩容时是极快的O(1)操作,执行时间仅几纳秒。但JMH每次调用基准方法时,都会产生额外开销:比如State对象的参数注入、Blackhole的消费逻辑、方法调用栈的创建销毁、线程状态维护等。

  • 对于单次add测试(代码示例2):每次基准方法调用只执行一次add(),JMH的框架开销会直接计入这次操作的时间统计。尤其是99.99%这类高百分位,会捕捉到框架开销带来的长尾延迟——比如偶尔的线程调度、JVM内部小停顿等,直接拉高了单次操作的百分位数值。
  • 对于批量循环测试(代码示例1):JMH仅需调用一次基准方法就能执行1亿次add(),框架开销被平摊到每一次操作上,几乎可以忽略。此时统计到的百分位,反映的是add()操作本身的真实执行延迟。

2. CPU执行效率的额外差异

批量循环的调用方式还有一个优势:连续的add()操作形成了规律的指令流,CPU的分支预测(ArrayList扩容判断分支绝大多数时候不会触发)、缓存命中率(数组元素的缓存复用)都会更高,流水线执行效率更优,进一步降低了add()操作的实际执行时间,让百分位数值更低。

代码示例

代码示例1:批量循环调用

@Benchmark
public void addItems(ThreadState state, Blackhole blackhole) {
    blackhole.consume(addItems(state.list, state.items, state.value));
}

private static boolean addItems(List<Long> list, int items, long value) {
    for (int i = 0; i < items; i++) {
        list.add(value);
    }
    return true;
}

代码示例2:单次调用

@Benchmark
public void add(ListState listState, Blackhole blackhole) {
    listState.list.add(listState.val);
}

建议

如果想要准确测量单个add()操作的真实性能,推荐使用批量循环+OperationsPerInvocation的方式,同时确保JMH的预热配置(@Warmup)足够充分,让JVM完成代码编译、缓存预热等优化。另外,统计百分位时要注意区分“基准方法调用时间”和“单个操作时间”,避免被框架干扰。

内容的提问来源于stack exchange,提问作者Abidi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:30:47