JMH - 如何测量将50M个项目插入ArrayList所需的时间

3

我有一个ArrayList包含5000万个对象,我想要测量将这么多对象存储在其中所需的时间。看起来所有JMH模式都是基于时间的,我们无法真正控制@Benchmark下的代码执行次数。例如,如何确保以下代码在每个分叉中精确地运行50M次?

@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(5)
public void run(BenchmarkState state) {
    try {
        state.queue.add(System.nanoTime());
    } catch (Exception e) {
        e.printStackTrace();
    }
}
1个回答

2
您可以创建一个基准类(ArrayListBenchmark)和一个运行器类(BenchmarkRunner)。
  • ArrayListBenchmark 类中,您可以添加基准方法,该方法迭代所需次数将项目添加到 List 中。
  • BenchmarkRunner 类中,您设置要添加到 List 中的项目数量,并配置运行器选项。
注意:根据您的环境,添加50M个项目可能会抛出 OutOfMemoryError
基准类:
import java.util.List;
import java.util.ArrayList;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;

public class ArrayListBenchmark {

    @State(Scope.Thread)
    public static class ThreadState {

        @Param({})
        private int items;

        private List<Long> list;

        @Setup(Level.Iteration)
        public void setup() {
            list = new ArrayList<>();
        }
    }

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

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

}

性能测试运行器类:
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;

public class BenchmarkRunner {

    private static final String ITEMS = "items";

    private static final String N_50_000_000 = "50000000";

    public static void main(String[] args) throws RunnerException {
        runArrayListBenchmark();
    }

    public static void runArrayListBenchmark() throws RunnerException {
        Options options = new OptionsBuilder()
                .include(ArrayListBenchmark.class.getSimpleName())
                .mode(Mode.AverageTime)
                .timeUnit(TimeUnit.NANOSECONDS)
                .warmupTime(TimeValue.seconds(1))
                .warmupBatchSize(1)
                .warmupIterations(5)
                .measurementTime(TimeValue.milliseconds(100))
                .measurementBatchSize(1)
                .measurementIterations(10)
                .param(ITEMS, N_50_000_000)
                .operationsPerInvocation(Integer.parseInt(N_50_000_000))
                .threads(1)
                .forks(5)
                .shouldFailOnError(true)
                .shouldDoGC(true)
                .resultFormat(ResultFormatType.CSV)
                .result("target/" + ArrayListBenchmark.class.getSimpleName().toLowerCase(Locale.ENGLISH) + ".csv")
                .build();
        new Runner(options).run();
    }

输出:

Result "ArrayListBenchmark.addItems":
  50.023 ±(99.9%) 0.768 ns/op [Average]
  (min, avg, max) = (48.094, 50.023, 53.020), stdev = 1.551
  CI (99.9%): [49.256, 50.791] (assumes normal distribution)

Benchmark                     (items)  Mode  Cnt   Score   Error  Units
ArrayListBenchmark.addItems  50000000  avgt   50  50.023 ± 0.768  ns/op

@Obeo 我们不让JMH执行我们的基准测试并跟踪执行次数,而是引入自己的循环来完成。JMH如何生成像针对50M插入的90、99、99.9百分位数这样的统计数据或其他模式(AvgTime、Throuput)的类似内容呢?它会认为每个基准测试是一个操作,而实际上已经执行了50M次操作。 - Abidi
1
@Abidi 不确定我是否理解了问题。您是想“测量将50M个项目插入ArrayList所需的时间”(如问题标题所述)吗?还是您对自己得到的输出结果有疑问?或者是其他什么问题? - Oboe
@Obeo 感谢您的回答。根据您提供的解决方案,我们将50M次执行捆绑到单个操作中。这意味着JMH无法提供其他指标,例如这50M次执行的平均、最小和最大时间,对吗?如果在JMH中不可能实现,那就没关系了,也许这是JMH的限制,或者它并不适用于像我这样的情况。我会将问题标记为已回答。 - Abidi
1
@Abidi 如果我理解正确的话,您应该在“选项”中将模式从“Mode.SingleShotTime”修改为“Mode.AverageTime”。此外,您应该通过添加“.operationsPerInvocation(50000000)”来设置每次调用的操作数(在您的情况下为50M)。我更新了答案以包括这些修改并添加了输出。 - Oboe
只要我将模式更改为AvgTime,如果我将measurementTime更改为5分钟,那么在run()方法下的代码就不会精确地执行50M次。这是否意味着您将时间设置为100ms只是为了触发基准测试,并且因为在run()方法完成时已经过去了100ms,所以JMH不会超过50M? - Abidi
@Abidi 从输出结果可以看出,答案中的代码被执行了5000万次。我建议您创建一个新问题,并提供能够重现问题的具体代码。 - Oboe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接