Java JMH Tool

15 minute read

개요

Java Microbenchmark Harness (JMH)는 Java microbenchmark를 생성하는 도구이다.

Benchmarking이 무엇인가?

컴퓨팅 분야에서 벤치마크(benchmark)는 프로그램을 실행하여 그것의 성능을 시험하는 테스트다.

쉽게 말해 코드가 얼마나 빨리/느리게 실행됐는지를 알려준다. (이와 다르게 프로파일링은 왜 이것이 느린지를 알려준다.)

이때, 마이크로 벤치마킹은 프로그램 전체 성능을 측정하는 것 보다, 특정 코드 블록이나 함수의 성능을 측정하는 것이다.

그러나 이러한 마이크로 벤치마크를 만드는 것은 쉽지 않다. JVM이나 하드웨어가 수행하는 최적화로 인해, 구현체의 실제 성능보다 좋게 측정될 수도 있다. JMH는 이러한 마이크로 벤치마킹을 도와주는 툴이다.

JMH 사용해보기

보통 벤치마크 프로젝트/클래스를 기존 애플리케이션과 분리해서 만드는 것이 이상적이라 한다. 그래서 다음 maven archtype 커맨드를 통해 JMH 프로젝트를 생성하였다.\

$ mvn archetype:generate \
  -DinteractiveMode=false \
  -DarchetypeGroupId=org.openjdk.jmh \
  -DarchetypeArtifactId=jmh-java-benchmark-archetype \
  -DgroupId=org.sample \
  -DartifactId=test \
  -Dversion=1.0

이미 진행 중인 프로젝트일 경우는 pom.xml에 JMH 디펜던시를 추가해주면 된다.

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.28</version>
        </dependency>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.28</version>
            <scope>provided</scope>
        </dependency>

archtype을 통해 생성하면 다음과 같이 MyBenchmark 클래스가 생성된다.

public class MyBenchmark {

    @Benchmark
    public void testMethod() {
        // This is a demo/sample template for building your JMH benchmarks. Edit as needed.
        // Put your benchmark code here.
    }

}

여기서, testMethod() 바디에 측정하고자 하는 코드를 넣을 수 있다. 어떠한 코드를 넣었다고 하면, 이제 빌드를 다음 커맨드로 할 수 있다.

$ mvn clean install

이 커맨드는 벤치마크 프로젝트 디렉토리에서 실행되어야 한다.

이 커맨드가 실행되면 first-benchmark/target에 JAR 파일(benchmarks.jar)이 생성된다. 이때, 이 JAR 파일은 우리의 벤치마크를 실행시키기 위해 모든 파일이 포함되어 있다. 즉, 컴파일된 벤치마크 클래스들과 벤치마크를 돌리기 위한 JMH 클래스들도 포함하고 있다.

빌드가 끝나면, 다음 커맨드를 통해 벤치마크를 실행할 수 있다.

$ java -jar target/benchmarks.jar

이 커맨드를 통해 벤치마크 클래스 대상으로 JMH가 시작된다. JMH는 우리의 코드를 스캔하여 벤치마크를 찾아서 실행시킨다. 그리고 결과를 커맨드 라인에 출력해준다.

벤치마크를 실행시키는 것은 시간이 조금 걸린다. JMH는 결과가 완전히 랜덤하지 않도록 여러번의 warm-up과 iteration을 수행한다. 더 많은 수행을 할수록 평균 성능/높은/낮은 성능치가 더 정확하게 나온다.

이를 실행할 때는 컴퓨터에서 다른 애플리케이션을 실행하지 않는 것이 좋다. 왜냐하면 CPU에 영향을 주기 때문에 성능이 부정확하게 나올 수 있기 때문이다.

JMH Benchmark Modes

JMH는 우리의 벤치마크를 여러가지 다른 모드로 실행할 수 있다. 벤치마크 모드는 JMH에게 측정하고자 하는 것을 알려준다. JMH에서는 다음과 같은 벤치마크 모드가 존재한다.

  • Throughput: 한 메소드가 특정한 시간 안에 몇 번 실행됐는지를 측정한다. Output값은 주로 연산 횟수/s로 나오고 여기서 연산은 벤치마크 메소드를 말한다.

  • AverageTime: 벤치마크 메소드 실행에 대한 평균 시간을 측정한다.

  • SampleTime: 벤치마크 메소드 실행에 대한 시간(max, min)을 측정한다.

  • SingleShotTime: 한 번의 벤치마크 메소드 실행이 얼마나 걸리는지 측정한다 (cold start로 실행, 즉 JVM warm up 없이).

  • All: 위의 모든 것들을 측정한다.

NOTE: Default 벤치마크는 Throughput이다.

어떠한 벤치마크 모드를 사용할지 명시하려면 JMH 어노테이션 @BenchmarkMode를 벤치마크 메소드 위에 쓴다. 예를 들어 다음과 같다.

public class MyBenchmark {

    @Benchmark @BenchmarkMode(Mode.Throughput)
    public void testMethod() {
        // This is a demo/sample template for building your JMH benchmarks. Edit as needed.
        // Put your benchmark code here.
    }

}

Benchmark Time Units

JMH은 벤치마크 결과를 어떠한 시간 단위로 출력할 것인지 명시할 수 있게 해준다.
@OutputTimeUnit 어노테이션을 사용하면되고, 이 어노테이션은 java.util.concurrent.TimeUnit을 매개변수로 받는다. 예를 들어 다음과 같다.

public class MyBenchmark {

    @Benchmark @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MINUTES)
    public void testMethod() {
        // This is a demo/sample template for building your JMH benchmarks. Edit as needed.
        // Put your benchmark code here.
    }

}

위 예시에서는 ‘분’ 단위로 명시하였다. 따라서 결과의 단위는 연산 횟수/min가 된다.

Benchmark State

만약에 벤치마크 메소드에서 필요한 어떠한 변수를 초기화하고 싶지만, 벤치마크가 측정하는 코드의 일부분으로 포함시키기 싫을 때, 이러한 변수를 state variable이라고 한다.

State 변수는 특별한 state 클래스에 선언되고, 이 클래스의 인스턴스는 벤치마크 메소드의 메소드로 전달될 수 있다. 예를 들어 다음과 같다.

public class MyBenchmark {

    @State(Scope.Thread)
    public static class MyState {
        public int a = 1;
        public int b = 2;
        public int sum;
    }

    @Benchmark @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MINUTES)
    public void testMethod(MyState state) {
        state.sum = state.a + state.b;
    }

}

위 예시의 경우, 중첩 static 클래스 MyState를 추가하였다. 그리고 MyState 클래스의 @State 어노테이션을 통해 JMH에게 이게 state 클래스라는 것을 알린다. 그리고 벤치마크 메소드인 testMethod에서 MyState의 인스턴스를 받는다.

State Scope

State 객체는 벤치마크 메소드 호출을 하는 여러 상황에서 재사용될 수 있다. JMH는 state 객체가 재사용될 수 있는 범위(scope) 세 가지를 제공한다. 위 예시의 경우에는 Scope.Thread로 선언하였다.
Scope 클래스는 다음과 같은 scope constant를 가진다.

  • Thread: 벤치마크를 실행하는 각 쓰레드는 자신만의 state 객체 인스턴스를 생성한다.

  • Group: 벤치마크를 실행하는 각 쓰레드 그룹은 자신들만의 state 객체 인스턴스를 생성한다.

  • Benchmark: 벤치마크를 실행하는 모든 쓰레드가 동일한 state 객체를 공유한다.

NOTE: JMH State 클래스를 만들 때는 다음 규칙을 따라야 한다.

  • 클래스는 public으로 선언되어야 한다.
  • 중첩 클래스일 경우, static 으로 선언되어야 한다.
  • public no-arg 생성자를 가져야 한다.

이 규칙을 따라야만 클래스 위에 @State 어노테이션을 써서 JMH가 state 클래스인 것을 인지할 수 있다.

State 클래스의 메소드에 @Setup과 @TearDown 어노테이션을 붙일 수 있다.

@Setup: 벤치마크 메소드한테 state 객체가 전달되기 전에 해당 메소드를 호출하여 state 객체를 셋업하라고 JMH에게 알려준다.

@TearDown: 벤치마크 메소드가 실행된 후에 해당 메소드를 호출하여 state 객체를 청소/처리하라고 JMH에게 알려준다.

NOTE: Setup과 TearDown의 실행 시간은 벤치마크 실행 측정에 포함되지 않는다.

예를 들어 다음과 같이 어노테이션을 작성할 수 있다.

public class MyBenchmark {

    @State(Scope.Thread)
    public static class MyState {
        
        @Setup(Level.Trial)
        public void doSetup() {
            sum = 0;
            System.out.println("Do Setup");
        }
        
        @TearDown(Level.Trial)
        public void doTearDown() {
            System.out.println("Do TearDown");
        }
        
        public int a = 1;
        public int b = 2;
        public int sum;
    }

    @Benchmark @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MINUTES)
    public void testMethod(MyState state) {
        state.sum = state.a + state.b;
    }

}

MyState 클래스에 doSetup()과 doTearDown() 메소드가 추가되었는데, 거기에 맞는 어노테이션이 각각 추가되었다. 위 예시에는 두 개의 메소드만 있지만 해당 어노테이션을 갖는 더 많은 메소드를 추가할 수도 있다.

그리고 이 어노테이션들은 매개변수를 받는다. 이 매개변수는 3종류가 있는데 JMH에게 언제 해당 메소드가 호출되어야 할지를 알려준다.

  • Level.Trial: default 레벨이다. 해당 메소드는 벤치마크의 각 전체 실행에 대해 한 번 불린다. (모든 warmup과 벤치마크 iteration을 포함한 full fork)

  • Level.Iteration: 해당 메소드는 벤치마크의 각 iteration에 대해 한 번 불린다.

  • Level.Invocation: 해당 메소드는 벤치마크 메소드 호출할 때마다 한 번씩 불린다.

실제로 위에서 학습한 내용을 토대로, ArrayList에 데이터를 넣은 후 반복문을 돌리는 작업의 성능을 측정해보자.

@State(Scope.Thread)
public class LoopTest {

    final int LIMIT_COUNT = 10000;
    final List<Integer> array = new ArrayList<>();

    @Setup
    public void init() {
        for (int i = 0; i < LIMIT_COUNT; i++) {
            array.add(i);
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public int testMethod() {
        int size = array.size();
        int result = 0;
        for (int i = 0; i < size; i++) {
            result = i;
        }
        return result;
    }

    public static void main(String[] args) throws IOException, RunnerException {
        Options opt = new OptionsBuilder()
                .include(LoopTest.class.getSimpleName())
                .warmupIterations(5)
                .measurementIterations(5)
                .forks(2)
                .build();
        new Runner(opt).run();
    }
}

NOTE: 여기에서는 중첩 static 클래스로 state 클래스를 만들지 않고 Top-level 클래스 자체가 state 클래스이다 (이렇게도 가능하다).

@Setup 어노테이션을 활용하여 배열 10000개의 포지션에 값을 넣어주고, 실제로 벤치마크 메소드에서는 해당 배열의 size를 가져와서 배열의 iterate하면서 result에 값을 대입해준다.

또, 여기서는 메인 메소드를 사용하여 Options 빌더를 통해 warmup iteration, measurement iteration, fork 횟수를 지정해준다. 그리고 Runner를 통해서 벤치마킹을 시작한다.

다음과 같은 결과가 출력된 것을 볼 수 있다.

# JMH version: 1.32
# VM version: JDK 1.8.0_282, OpenJDK 64-Bit Server VM, 25.282-b08
# VM invoker: C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.282-1\jre\bin\java.exe
# VM options: -javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.1.1\lib\idea_rt.jar=55193:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.1.1\bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.LoopTest.testMethod

# Run progress: 0.00% complete, ETA 00:03:20
# Fork: 1 of 2
# Warmup Iteration   1: 432710.348 ops/ms
# Warmup Iteration   2: 439915.626 ops/ms
# Warmup Iteration   3: 505490.117 ops/ms
# Warmup Iteration   4: 510343.269 ops/ms
# Warmup Iteration   5: 510511.269 ops/ms
Iteration   1: 505314.335 ops/ms
Iteration   2: 504381.053 ops/ms
Iteration   3: 507891.629 ops/ms
Iteration   4: 504376.622 ops/ms
Iteration   5: 507469.551 ops/ms

# Run progress: 50.00% complete, ETA 00:01:40
# Fork: 2 of 2
# Warmup Iteration   1: 436687.389 ops/ms
# Warmup Iteration   2: 431899.492 ops/ms
# Warmup Iteration   3: 491948.058 ops/ms
# Warmup Iteration   4: 500654.608 ops/ms
# Warmup Iteration   5: 494051.290 ops/ms
Iteration   1: 498464.356 ops/ms
Iteration   2: 502546.375 ops/ms
Iteration   3: 495694.160 ops/ms
Iteration   4: 504518.949 ops/ms
Iteration   5: 505349.634 ops/ms


Result "org.sample.LoopTest.testMethod":
  503600.666 ±(99.9%) 5777.603 ops/ms [Average]
  (min, avg, max) = (495694.160, 503600.666, 507891.629), stdev = 3821.527
  CI (99.9%): [497823.064, 509378.269] (assumes normal distribution)


# Run complete. Total time: 00:03:21

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark             Mode  Cnt       Score      Error   Units
LoopTest.testMethod  thrpt   10  503600.666 ± 5777.603  ops/ms

Process finished with exit code 0

ImpactDiff 벤치마크

ImpactDiff의 여러 메소드들의 벤치마크를 만들기 위해 다음과 같이 진행하였다.

  1. ImpactDiff에 있는 클래스들에 접근이 가능해야 하기 때문에 해당 프로젝트를 jar로 export하였다.

  2. Benchmark 프로젝트에서 1번에서 만든 jar을 external libraries에 추가해주었다.

NOTE: 둘 다 maven 빌드 툴을 사용하기 때문에 ImpactDiff를 mvn install로 로컬에 jar 파일을 만들어서 Benchmark 프로젝트에서 디펜던시를 추가해주는 방식으로 하려고 하였으나, ImpactDiff mvn install 과정에서 로컬 넥서스 서버에 접근할 수 없어서 실패하였다.(실제로 이런 방식과 external libraries에 jar을 인텔리제이상에서 추가 해주는 차이점이 궁금함. Maven은 인텔리제이의 external jars를 사용하지 않기 때문에 local repo에 mvn install을 하여 pom에 디펜던시로 추가해야 한다고 함)

추가: 실제로 위에서 말한대로 mvn pom.xml에 디펜던시를 추가해주었더니 external libraries에서 정상적으로 jar 파일이 추가가 되었다.

의문: 회사 컴퓨터에서 impactdiff 프로젝트에서 mvn install을 어떻게 했는지? fasoo logger nexus 연결 문제 어떻게 했는지?

IDE 상으로 메인 메소드로 Runner 객체를 통해 실행할 수도 있지만 약간의 측정 오차가 존재할 수 있다. 추천된 실행 방법은 maven으로 install하면 알아서 shade plugin을 사용하여 jar 파일이 생성되는데 이를 커맨드에서 돌리는 방법이다.

결과는 다음과 같다.

# JMH version: 1.32
# VM version: JDK 11.0.8, OpenJDK 64-Bit Server VM, 11.0.8+10-b944.6842174
# VM invoker: D:\Program Files\Android Studio\jre\bin\java.exe
# VM options: <none>
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.ImpactDiffBenchmark.testMethod

# Run progress: 0.00% complete, ETA 00:25:00
# Fork: 1 of 5
# Warmup Iteration   1: 1.551 ops/ms
# Warmup Iteration   2: 1.603 ops/ms
# Warmup Iteration   3: 1.604 ops/ms
# Warmup Iteration   4: 1.611 ops/ms
# Warmup Iteration   5: 1.616 ops/ms
Iteration   1: 1.588 ops/ms
Iteration   2: 1.612 ops/ms
Iteration   3: 1.570 ops/ms
Iteration   4: 1.566 ops/ms
Iteration   5: 1.553 ops/ms

# Run progress: 6.67% complete, ETA 01:11:52
# Fork: 2 of 5
# Warmup Iteration   1: 1.599 ops/ms
# Warmup Iteration   2: 1.616 ops/ms
# Warmup Iteration   3: 1.585 ops/ms
# Warmup Iteration   4: 1.592 ops/ms
# Warmup Iteration   5: 1.583 ops/ms
Iteration   1: 1.602 ops/ms
Iteration   2: 1.609 ops/ms
Iteration   3: 1.548 ops/ms
Iteration   4: 1.583 ops/ms
Iteration   5: 1.585 ops/ms

# Run progress: 13.33% complete, ETA 01:06:15
# Fork: 3 of 5
# Warmup Iteration   1: 1.617 ops/ms
# Warmup Iteration   2: 1.618 ops/ms
# Warmup Iteration   3: 1.624 ops/ms
# Warmup Iteration   4: 1.634 ops/ms
# Warmup Iteration   5: 1.634 ops/ms
Iteration   1: 1.624 ops/ms
Iteration   2: 1.604 ops/ms
Iteration   3: 1.611 ops/ms
Iteration   4: 1.628 ops/ms
Iteration   5: 1.611 ops/ms

# Run progress: 20.00% complete, ETA 01:01:20
# Fork: 4 of 5
# Warmup Iteration   1: 1.603 ops/ms
# Warmup Iteration   2: 1.606 ops/ms
# Warmup Iteration   3: 1.614 ops/ms
# Warmup Iteration   4: 1.608 ops/ms
# Warmup Iteration   5: 1.597 ops/ms
Iteration   1: 1.610 ops/ms
Iteration   2: 1.615 ops/ms
Iteration   3: 1.598 ops/ms
Iteration   4: 1.576 ops/ms
Iteration   5: 1.549 ops/ms

# Run progress: 26.67% complete, ETA 00:56:02
# Fork: 5 of 5
# Warmup Iteration   1: 1.616 ops/ms
# Warmup Iteration   2: 1.617 ops/ms
# Warmup Iteration   3: 1.605 ops/ms
# Warmup Iteration   4: 1.584 ops/ms
# Warmup Iteration   5: 1.607 ops/ms
Iteration   1: 1.594 ops/ms
Iteration   2: 1.597 ops/ms
Iteration   3: 1.633 ops/ms
Iteration   4: 1.649 ops/ms
Iteration   5: 1.654 ops/ms


Result "org.sample.ImpactDiffBenchmark.testMethod":
  1.599 ▒▒(99.9%) 0.021 ops/ms [Average]
  (min, avg, max) = (1.548, 1.599, 1.654), stdev = 0.028
  CI (99.9%): [1.578, 1.620] (assumes normal distribution)


# JMH version: 1.32
# VM version: JDK 11.0.8, OpenJDK 64-Bit Server VM, 11.0.8+10-b944.6842174
# VM invoker: D:\Program Files\Android Studio\jre\bin\java.exe
# VM options: <none>
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.LoopTest.testMethod

# Run progress: 33.33% complete, ETA 00:51:00
# Fork: 1 of 5
# Warmup Iteration   1: 416120.725 ops/ms
# Warmup Iteration   2: 352844.027 ops/ms
# Warmup Iteration   3: 473099.997 ops/ms
# Warmup Iteration   4: 478057.447 ops/ms
# Warmup Iteration   5: 475131.196 ops/ms
Iteration   1: 474486.394 ops/ms
Iteration   2: 479170.182 ops/ms
Iteration   3: 472624.065 ops/ms
Iteration   4: 475090.453 ops/ms
Iteration   5: 470581.868 ops/ms

# Run progress: 40.00% complete, ETA 00:40:46
# Fork: 2 of 5
# Warmup Iteration   1: 415548.840 ops/ms
# Warmup Iteration   2: 347890.662 ops/ms
# Warmup Iteration   3: 476991.687 ops/ms
# Warmup Iteration   4: 479953.897 ops/ms
# Warmup Iteration   5: 478497.521 ops/ms
Iteration   1: 479580.060 ops/ms
Iteration   2: 480740.984 ops/ms
Iteration   3: 469712.559 ops/ms
Iteration   4: 476352.638 ops/ms
Iteration   5: 480556.055 ops/ms

# Run progress: 46.67% complete, ETA 00:32:58
# Fork: 3 of 5
# Warmup Iteration   1: 421609.276 ops/ms
# Warmup Iteration   2: 421961.144 ops/ms
# Warmup Iteration   3: 482889.326 ops/ms
# Warmup Iteration   4: 471646.682 ops/ms
# Warmup Iteration   5: 480078.294 ops/ms
Iteration   1: 471618.859 ops/ms
Iteration   2: 472526.589 ops/ms
Iteration   3: 479672.909 ops/ms
Iteration   4: 480902.873 ops/ms
Iteration   5: 480101.910 ops/ms

# Run progress: 53.33% complete, ETA 00:26:42
# Fork: 4 of 5
# Warmup Iteration   1: 418879.113 ops/ms
# Warmup Iteration   2: 420986.119 ops/ms
# Warmup Iteration   3: 476023.942 ops/ms
# Warmup Iteration   4: 481368.968 ops/ms
# Warmup Iteration   5: 478734.929 ops/ms
Iteration   1: 474144.119 ops/ms
Iteration   2: 477336.617 ops/ms
Iteration   3: 472049.513 ops/ms
Iteration   4: 480754.212 ops/ms
Iteration   5: 477535.890 ops/ms

# Run progress: 60.00% complete, ETA 00:21:28
# Fork: 5 of 5
# Warmup Iteration   1: 421707.213 ops/ms
# Warmup Iteration   2: 421644.614 ops/ms
# Warmup Iteration   3: 483383.391 ops/ms
# Warmup Iteration   4: 475849.435 ops/ms
# Warmup Iteration   5: 473748.067 ops/ms
Iteration   1: 477018.042 ops/ms
Iteration   2: 472158.760 ops/ms
Iteration   3: 477056.214 ops/ms
Iteration   4: 477206.204 ops/ms
Iteration   5: 478129.691 ops/ms


Result "org.sample.LoopTest.testMethod":
  476284.307 ▒▒(99.9%) 2656.848 ops/ms [Average]
  (min, avg, max) = (469712.559, 476284.307, 480902.873), stdev = 3546.816
  CI (99.9%): [473627.458, 478941.155] (assumes normal distribution)


# JMH version: 1.32
# VM version: JDK 11.0.8, OpenJDK 64-Bit Server VM, 11.0.8+10-b944.6842174
# VM invoker: D:\Program Files\Android Studio\jre\bin\java.exe
# VM options: <none>
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.MyBenchmark.testMethod

# Run progress: 66.67% complete, ETA 00:16:56
# Fork: 1 of 5
# Warmup Iteration   1: Do Setup
110527011199.788 ops/min
# Warmup Iteration   2: 111729747411.854 ops/min
# Warmup Iteration   3: 110603280385.723 ops/min
# Warmup Iteration   4: 110958841248.046 ops/min
# Warmup Iteration   5: 111203023251.724 ops/min
Iteration   1: 109534708797.311 ops/min
Iteration   2: 110923256396.142 ops/min
Iteration   3: 110991336499.476 ops/min
Iteration   4: 111033109377.952 ops/min
Iteration   5: Do TearDown
110462085906.607 ops/min

# Run progress: 73.33% complete, ETA 00:12:55
# Fork: 2 of 5
# Warmup Iteration   1: Do Setup
110930294734.673 ops/min
# Warmup Iteration   2: 111890574228.237 ops/min
# Warmup Iteration   3: 111752225167.347 ops/min
# Warmup Iteration   4: 111136306851.683 ops/min
# Warmup Iteration   5: 111273303189.496 ops/min
Iteration   1: 110759292487.597 ops/min
Iteration   2: 110858526645.709 ops/min
Iteration   3: 110383446873.075 ops/min
Iteration   4: 110438892037.022 ops/min
Iteration   5: Do TearDown
111040384638.219 ops/min

# Run progress: 80.00% complete, ETA 00:09:18
# Fork: 3 of 5
# Warmup Iteration   1: Do Setup
111326810892.927 ops/min
# Warmup Iteration   2: 110970612846.438 ops/min
# Warmup Iteration   3: 110215306827.731 ops/min
# Warmup Iteration   4: 110811578038.997 ops/min
# Warmup Iteration   5: 110892730308.418 ops/min
Iteration   1: 109980875210.168 ops/min
Iteration   2: 111115416978.498 ops/min
Iteration   3: 110597677776.804 ops/min
Iteration   4: 111252132460.429 ops/min
Iteration   5: Do TearDown
111170899110.048 ops/min

# Run progress: 86.67% complete, ETA 00:05:59
# Fork: 4 of 5
# Warmup Iteration   1: Do Setup
111842772707.929 ops/min
# Warmup Iteration   2: 110685133993.920 ops/min
# Warmup Iteration   3: 109730158353.617 ops/min
# Warmup Iteration   4: 110289784477.957 ops/min
# Warmup Iteration   5: 111288413598.274 ops/min
Iteration   1: 111134192699.078 ops/min
Iteration   2: 111271662821.243 ops/min
Iteration   3: 111302525280.929 ops/min
Iteration   4: 111207754695.228 ops/min
Iteration   5: Do TearDown
111192355216.274 ops/min

# Run progress: 93.33% complete, ETA 00:02:53
# Fork: 5 of 5
# Warmup Iteration   1: Do Setup
111136484301.584 ops/min
# Warmup Iteration   2: 111250609398.617 ops/min
# Warmup Iteration   3: 111387732522.883 ops/min
# Warmup Iteration   4: 110860960548.020 ops/min
# Warmup Iteration   5: 111019784729.109 ops/min
Iteration   1: 110941947300.578 ops/min
Iteration   2: 110842484609.882 ops/min
Iteration   3: 111039540246.666 ops/min
Iteration   4: 110741723060.346 ops/min
Iteration   5: Do TearDown
109775916831.214 ops/min


Result "org.sample.MyBenchmark.testMethod":
  110799685758.260 ▒▒(99.9%) 354654906.758 ops/min [Average]
  (min, avg, max) = (109534708797.311, 110799685758.260, 111302525280.929), stdev = 473454153.760
  CI (99.9%): [110445030851.502, 111154340665.018] (assumes normal distribution)


# Run complete. Total time: 00:42:14

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                        Mode  Cnt             Score           Error    Units
ImpactDiffBenchmark.testMethod  thrpt   25             1.599 ▒▒         0.021   ops/ms
LoopTest.testMethod             thrpt   25        476284.307 ▒▒      2656.848   ops/ms
MyBenchmark.testMethod          thrpt   25  110799685758.260 ▒▒ 354654906.758  ops/min

팀장님 피드백: 소스코드 10만 라인 비교하는데 평균 몇초 뭐 이런식으로 결론을 낼 수 있을까?

내 궁금증: 같은 10만 라인을 비교하여도, 비교되는 대상에 따라 성능이 달라지지 않을까?

출처

https://medium.com/javarevisited/understanding-java-microbenchmark-harness-or-jmh-tool-5b9b90ccbe8d https://github.com/openjdk/jmh http://tutorials.jenkov.com/java-performance/jmh.html https://soshace.com/benchmark-java-applications-using-jmh/

Updated:

Leave a comment