[Java] Java 성능 측정 도구 JMH 알아보기

728x90

안녕하세요 👐

오늘은 성능측정도구 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 코드에 대한 성능을 측정하는 방법을 알았습니다.

 

이를 통해서 저는 제 코드를  직접 성능을 체크해 보며 좋은 코드인지 나쁜코드인지 확인하는 방법을 알았고,

상황에 맞게 더 좋은 코드를 짜기 위해 노력하게 되는 계기가 될 것이며, 실무 또는 공부할 때 유용하게 사용할 것 같습니다.

728x90