Java JMH 라이브러리를 활용한 성능테스트

DongHyun Kim·2023년 8월 9일
0

실행 환경
Gradle 7.5
Java 17
Intellij

  • Maven 으로 설정한 JMH 는 많이 보였기 때문에 Gradle 로 진행했습니다

JMH 사용 목적 🚩

어떤 자바 메서드를 개선했다고 가정했을 때, 이를 메서드 단위로 수치화하여 어느 정도의 개선이 이루어졌는지 계산해야한다. 이를 가능하게 하는 방법 중에

첫 째로, System.currentTimeMillis() 이나 System.nanoTime() 메서드를 통해 시작과 끝의 차이를 계산하여 성능을 비교하는 방법이 있다.

둘 째로, JMH (Java Microbenchmark Harness) 는 자바 메서드의 성능을 측정하기 위한 라이브러리로, 어노테이션만으로 성능을 측정할 수 있기 때문에 보일러 플레이트 없이 정확하게 측정할 수 있다.

디렉토리 구조 🌲

src 폴더 아래에 jmh 디렉토리에 벤치마크할 코드가 있어야 한다.
설치할 jmh plugin 이 벤치마크에 필요한 설정들을 자동으로 생성해서 src 아래의 jmh 폴더에서 벤치마크할 코드를 찾는다.

jmh 플러그인을 사용하면 별도의 프로젝트를 만들지 않고도 기존 소스를 쉽게 테스트 할 수 있다. 그렇기 때문에 벤치마크 소스 파일을 src/main/java 대신 src/jmh/java 에 넣어야 하는 이유이다.

build.gradle 설정 ⚙️

plugins {
	id 'java'
    id "me.champeau.jmh" version "0.7.1"
}

dependencies {
    jmh 'org.openjdk.jmh:jmh-core:0.9'
    jmh 'org.openjdk.jmh:jmh-generator-annprocess:0.9'
}

jmh{
    fork = 1
    warmupIterations = 1
    iterations = 1
}

jmh 플러그인은 자동으로 자기가 사용할 버젼의 org.openjdk.jmh 의 jmh-core 와 jmh-generator-annprocess 를 설치하기 때문에 밑에 dependencies 안의 코드는 작성하지 않아도 된다. 만약 임의로 지정한다면 지정한 JMH 버전이 대신 설치된다.
참고로, 0.9 버전이 호환이 안되는지 빌드할 때 계속 오류가 나서 지우고 실행했다
또한, jmh 0.7.1 버전의 플러그인은 JMH 1.3.6 을 사용한다고 한다.

jmh { } 코드 안에는 벤치마크 할 때 Configuration oprions 을 설정할 수 있다.

더 자세한건 jmh-gradle-plugin 를 참고!

테스트 할 JAVA 코드 💻

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.All)
public class ParallelStreamBenchmark {

    private static final long N = 10_000_000L;

//    Benchmark method should return something and no parameter requires
    @Benchmark
    public long iterateSum(){
        long sum = 0;
        for(long i = 1L; i <= N; i++){
            sum += i;
        }
        return sum;
    }

    @Benchmark
    public long parallelSum(){
        return Stream.iterate(1L,i -> i+1)
                .limit(N)
                .parallel() // make stream parallel
                .reduce(0L, Long::sum);
    }

//    public static void main(String[] args) throws Exception {
//        org.openjdk.jmh.Main.main(args);               // 벤치마킹 시작 --> 필요 X!
//    }

}

코드를 대강 설명하자면, 순차적으로 합을 구한 iterateSum 과 병렬로 합을 구한 parallelSum 의 효율을 계산한 코드이다.

벤치마크할 메서드들의 특징으로, 매개변수가 없어야되고 리턴 값이 void가 아니어야한다!
매개변수를 사용하는 대신 @Setup 을 이용하여 변수 값을 초기화할 수 있다.
리턴값이 void라면 대신 Blackhole 객체를 매개변수에서 생성해서 consume 메서드를 이용해야한다.
참고

@State(Scope.Benchmark)
같은 Benchmark 끼리는 같은 객체를 공유한다.
이걸 이용하면 멀티쓰레드 환경일 때 성능을 측정하기 좋다

@OutputTimeUnit(TimeUnit.MICROSECONDS)
벤치마킹 결과를 보여줄 단위를 설정한다. 위 설정은 ms 로 보여준다

@BenchmarkMode(Mode.All)
JMH 의 어떤 벤치마크를 실행할지 지정해준다

@Benchmark
벤치마킹할 메서드에 붙인다.

더 다양한 설정과 예제 코드는 여기서 java-performance jmn 확인 가능하다.

Benchmark 실행 🥳

Benchmark 하기 위해선 gradle 을 이용해 빌드해야 결과를 볼 수 있다.
gradlew 가 있는 루트 디렉토리로 이동해서
./gradlew jmh 를 입력하면 빌드가 시작되면서 벤치마킹 결과를 볼 수 있다

생성된 jar 파일은 {프로젝트}/build/libs 위치에 jar 파일이 있고
java -jar {jar파일명}.jar 로 실행해서 빌드 결과를 볼 수 있다.

profile
do programming yourself

2개의 댓글

comment-user-thumbnail
2023년 8월 9일

공감하며 읽었습니다. 좋은 글 감사드립니다.

1개의 답글