안녕하세요 👐
오늘은 성능측정도구 JMH 에 대한 이야기를 해보려고 합니다.
서론
제가 JMH 를 알게 된 계기는 자바에서 Stream 과 일반 for loop 에 대한 성능 측정을 해보고
둘에 대한 차이를 알아보다가 자연스럽게 알게 되었습니다.
JMH 라이브러리는 간단하고, 어노테이션 기반 방식을 지원하며, 안정적으로 자바 프로그램 또는 JVM 메모리를 사용하는
모든 언어를 대상으로 벤치마크를 구현할 수 있습니다.
벤치마크라는 단어가 생소 했습니다. 맨처음엔 mysql workbench 랑 헷갈렸었죠..😅
벤치마크는 컴퓨터 시스템, 소프트웨어, 하드웨어, 디바이스 등으 성능을 측정하고 비교하기 위해 사용되는 표준화된 테스트 입니다.
이 테스트는 일반적으로 속도,처리량,응답시간,성능 과 같은 측면을 측정하여 전체적인 성능을 평가합니다.
벤치마크 결과는 본인 PC 사양에 따라 달라질수 있고, 동일 코드를 내 PC에서 테스트 하는것과, 다른 PC에서 테스트 해도 PC 자체 성능에 차이로 결과가 달라질 수 있습니다.
그리고 벤치마크를 수행하는 동안 컴퓨터에 과부하를 가하기 때문에 문제가 생길 수 있으니, 미리 대응할 준비를 해야합니다...
이제 본론으로 들어가 보겠습니다.
본론
참고로 제 실행 환경은 Intellj, Java17, Gradle8.x 환경에서 하고 있습니다.
벤치마크를 사용하기 위해서는 JMH 라이브러리를 가져와야 합니다.
https://github.com/openjdk/jmh
GitHub - openjdk/jmh: https://openjdk.org/projects/code-tools/jmh
https://openjdk.org/projects/code-tools/jmh. Contribute to openjdk/jmh development by creating an account on GitHub.
github.com
위 오픈 소스에서 가져올 수 있습니다.
또는 버전이 안맞거나 문제가 있는 분은 Maven Repository 에서 가져올 수 있습니다.
build.gradle
plugins {
id 'java'
id "me.champeau.jmh" version "0.7.2" // 추가
}
group = 'org.example'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
// JMH 추가 -> 테스트에서 돌리려면 추가해야하는 듯?
testImplementation group: 'org.openjdk.jmh', name: 'jmh-core', version: '1.37'
testImplementation group: 'org.openjdk.jmh', name: 'jmh-generator-annprocess', version: '1.37'
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
test {
useJUnitPlatform()
}
// 미리 JMH 설정 해두기 위한 것
jmh {
fork = 1
warmupIterations = 1
iterations =1
}
그냥 위 코드를 복붙해서 사용하시면 됩니다.
그리고 본인에 맞는 설정을 해주기 위해서는
jmh {
fork = 1
warmupIterations = 1
iterations =1
}
위 부분만 바꿔주면 될 것 입니다.
그리고 대망의 ⭐️ 중요한 폴더 경로 입니다 . (제가 이걸 몰라서 에러 찾느라 시간을 좀 허비했습니다..)
폴더 경로는 기본 gradle 프로젝트를 생성하면
보통 위 구조일 것입니다.
❌ JMH 는 위 구조를 사용하면 안됩니다❌.
JMH 라이브러리 에서 권장하는 폴더 구조가 있기 때문에 그에 맞춰서 해야합니다.
위 처럼
src / jmh / java / 원하는 패키지 이름 / java 파일
위 구조를 해야합니다. 안그러면 자꾸 에러가 발생합니다ㅠㅠ
그럼 위 세팅이 끝났으면 이제 예시 코드를 보겠습니다.
package benchmark;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.TearDown;
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 벤치마크 결과를 밀리초 단위로 출력
@BenchmarkMode(Mode.AverageTime) // 벤치마크 대상 메소드 실행하는데 걸린 평균 시간 측정
@Fork(value = 2, jvmArgs = {"-Xms4G","-Xmx4G"}) // 4Gb 의 힙 공간을 제공한 환경에서 두번 벤치마크 수행해 결과의 신뢰성 확보
public class ParallelStreamBenchmark {
private static final long N = 10_000_000L;
@Benchmark
public long sequentialSum() {
return Stream.iterate(1L, i-> i+1)
.limit(N)
.reduce(0L, Long::sum);
}
@Benchmark
public long iterativeSum() {
long result = 0;
for (long i = 1L; i <N ; i++) {
result += i;
}
return result;
}
@TearDown(Level.Invocation) // 벤치마크 실행 후 가비지 컬렉터 동작
public void reset() {
System.gc();
}
}
제가 원하는 건 for loop 와 Stream 의 성능 비교였습니다.
그리고 실행은 본인 벤치마크 파일 최상위 경로 즉 프로젝트 시작하는 곳 터미널 에서
./gradlew jmh
위 명령어를 입력하면 gradle 이 jmh 를 실행시킵니다.
위 명령어가 실행이 되면서
성공적으로 테스트가 진행되는걸 볼 수 있습니다.
그 결과는
순차적 스트림보다, for loop 가 더 빠른 것을 확인할 수 있었습니다.
이 포스팅은 그 내용을 적는게 아니기 때문에 딥한 내용은 다음에 포스팅하겠습니다.
결론
오늘은 JMH 라이브러리릍 통해서 Java 코드에 대한 성능을 측정하는 방법을 알았습니다.
이를 통해서 저는 제 코드를 직접 성능을 체크해 보며 좋은 코드인지 나쁜코드인지 확인하는 방법을 알았고,
상황에 맞게 더 좋은 코드를 짜기 위해 노력하게 되는 계기가 될 것이며, 실무 또는 공부할 때 유용하게 사용할 것 같습니다.