본문 바로가기
Java/Java환경구성

Java-jmh(벤치마킹 툴) 환경구성하기

by ms727 2025. 2. 4.

개요

Java 테스트 관련 글을 읽다가 어떤 글에서 jmh라는 벤치마킹툴을 사용하는것을 보았습니다.

한 번 구성해봐야겠다고 생각했습니다.

jmh 벤치마킹툴에 대한 설명들은 여러 블로그에 있으니, 참고부탁드립니다.

요구사항

요구사항은 다음과 같습니다.

  • 가장 큰 숫자의 값을 가지고 있는 String 객체
  • 이 String 객체를 여러 방법으로 숫자로 변환하였을때 걸리는 평균시간들을 측정

1. 의존성 추가

//benchmark
implementation 'org.openjdk.jmh:jmh-core:1.37' //core
annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37'//annotation

jmh코어 부분과 애노테이션 관련 라이브러리를 추가해줍니다.

2. 코드 작성

그 후 바로 테스트할 코드를 작성하면 됩니다.

어떤 블로그에서는 /src/test 경로에 @Test애노테이션을 추가하여 jmh을 실행하는데, 그리 추천하지는 않습니다.
왜냐하면 Junit과 테스트가 겹칠 위험도 있고 jmh자체가 Junit을 통한 실행을 공식적으로 지원하지 않기 떄문입니다.

일반적이지는 않지만 Java는 n개 이상의 main()함수를 가질 수 있습니다. 이 방식을 활용하여 간단한 테스트를 진행할 수 있는데 이 방식을 사용하겠습니다.

public class CheckStringNumberBenchMarking {
    private static final String TEST_VALUE = String.valueOf(Integer.MAX_VALUE);

    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
                .include(CheckStringNumberBenchMarking.class.getSimpleName()) // 클래스명을 문자열로 가져와 실행 대상 선택
                .mode(Mode.AverageTime) // 평균시간 측정
                .timeUnit(TimeUnit.NANOSECONDS) // 단위는 ns
                .warmupTime(TimeValue.seconds(1)) // 워밍업 각 반복당 실행시간 지정
                .warmupIterations(2) // 벤치 마크 실행하기전 2번의 워밍업 진행
                .measurementTime(TimeValue.seconds(1)) // 측정 단계에서 각 반복당 실행시간 지정
                .measurementIterations(20) // 벤치마크를 n번 실행하여 평균값을 측정
                .threads(1) // 쓰레드 갯수
                .forks(1) // JVM 갯수 지정
                .shouldFailOnError(true) // 벤치마크 실행 중 오류가 발생하면 실행 중단할지 여부 확인
                .shouldDoGC(true) // 각 벤치마크 실행 전 GC를 실행할지 여부
                .build();
        new Runner(opt).run();
    }

    @Benchmark
    public boolean usingCoreJava() {
        try {
            Integer.parseInt(TEST_VALUE);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    @Benchmark
    public boolean usingNumberUtils_isCreatable() {
        return NumberUtils.isCreatable(TEST_VALUE);
    }

    @Benchmark
    public boolean usingRegularExpressions() {
        return TEST_VALUE.matches("-?\\d+(\\.\\d+)?");
    }

    @Benchmark
    public boolean usingNumberUtils_isParsable() {
        return NumberUtils.isParsable(TEST_VALUE);
    }

    @Benchmark
    public boolean usingStringUtils_isNumeric() {
        return StringUtils.isNumeric(TEST_VALUE);
    }

    @Benchmark
    public boolean usingStringUtils_isNumericSpace() {
        return StringUtils.isNumericSpace(TEST_VALUE);
    }

}

참고로 벤치마크 실행 전 워밍업 단계를 가지는 이유는 JVM 최적화 시간을 주기 위함입니다.

3. 결과

실행하는 법은 IDE에서 main함수를 찾아서 알아서 실행해주긴 하지만 command-line으로도 실행가능합니다.

java CheckStringNumberBenchMarking
Benchmark                                                      Mode  Cnt    Score    Error  Units
CheckStringNumberBenchMarking.usingCoreJava                    avgt   20    6.841 ±  0.313  ns/op
CheckStringNumberBenchMarking.usingNumberUtils_isCreatable     avgt   20    8.421 ±  0.348  ns/op
CheckStringNumberBenchMarking.usingNumberUtils_isParsable      avgt   20    0.335 ±  0.013  ns/op
CheckStringNumberBenchMarking.usingRegularExpressions          avgt   20  176.956 ± 10.107  ns/op
CheckStringNumberBenchMarking.usingStringUtils_isNumeric       avgt   20    0.337 ±  0.012  ns/op
CheckStringNumberBenchMarking.usingStringUtils_isNumericSpace  avgt   20    0.329 ±  0.012  ns/op

이 툴의 한계점이 있는데, 일단 실행환경에 의존적입니다. 실제 운영환경에서는 성능이 다르게 측정될 수 있습니다.
또한 테스트 환경 구축도 번거로움이 있지만 네트워크 I/O, DB I/O등에 대한 성능 측정에는 한계가 있습니다.