개요
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등에 대한 성능 측정에는 한계가 있습니다.