ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 🧪 JMH (Java Microbenchmark Harness)
    ☕️ java 2023. 10. 23. 13:49

    자바에서 제공하는 Random 함수에 대해 공부하는 과정에서

    java.util.Random 과 java.util.concurrent.ThreadLocalRandom 의 성능을 비교해보고자 JMH라는 개념을 도입하고자 공부를 하게 되었다.

     

    ⭐ JMH


    📍 JMH(Java Microbenchmark Harness)는 openjdk에서 만든 라이브러리이다.

    - JVM 상에서 실행되는 코드의 성능을 측정하기 위한 벤치마크 도구이다.

     

    📍 성능 측정

    • 코드 실행 시간
    • 메모리 사용량
    • GC 활동
    • 등등

    ⇒ 측정하고 수행하여 실행 결과에 대한 통계를 제공한다.

     

    📖 벤치마크 (Benchmark)

    • 컴퓨터 시스템, 소프트웨어, 하드웨어 등의 성능을 측정하고 비교하기 위해 사용되는 표준화된 기준이나 테스트

     

    🔍 Annotation

     

    1️⃣ @BenchmarkMode

    - JMH는 벤치마크를 다양한 방법으로 수행할 수 있다.

     

    Mode Description
    Throughput 초당 작업수 측정, 기본값
    AverageTime 작업이 수행되는 평균 시간 측정
    SampleTime 최대, 최소 시간 등 작업이 수행하는데 걸리는 시간 측정
    SingleShotTime 단일 작업 수행 소요 시간 측정
    All 위 모든 시간을 측정

     

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    pulic void test() {
    	  ...
    }

     

    2️⃣ @OutputTimeUnit

    - 벤치마크 결과를 출력할 시간 단위를 설정할 수 있다.

    • java.util.concurrent.TimeUnit 열거 타입을 통해 설정 가능
    @Benchmark 
    @BenchmarkMode(Mode.Throughput) 
    @OutputTimeUnit(TimeUnit.MINUTES)
    public void testMethod() {
        ...
    }

     

    3️⃣ @State

    - JMH는 벤치마크 코드에서 필요한 일부 변수의 상태를 지정할 수 있다.

    • 테스트 상황에 따라 초기화를 해야하거나 값이 유지되어야 하는 경우가 발생

     

    Scope Description
    Thread Thread 별 인스턴스 생성
    Benchmark 동일 테스트내 모든 Thread에서 동일 Instance를 공유
    Group Thread 그룹마다 Instance를 생성

     

    ⚠️ @State 어노테이션이 적용되는 클래스는 아래와 같은 규칙을 준수해야 함.

    • 클래스는 public으로 선언되어야 한다.
    • 중첩 클래스인 경우 static으로 선언해야 한다 (public static class .. )
    • public 기본 생성자를 가지고 있어야 한다.

     

    public class MyBenchmark {
    
        @State(Scope.Thread)
        public static class MyState {
            public int a = 1;
            public int b = 2;
            public int sum ;
        }
    
        @Benchmark 
    		@BenchmarkMode(Mode.Throughput) 
    		@OutputTimeUnit(TimeUnit.MINUTES)
        public void testMethod(MyState state) {
            state.sum = state.a + state.b;
        }
    
    }
    

     

    3️⃣ @Setup / @TearDown

    - 상태 클래스의 method에 적용가능

     

    @Setup 은 JMH에게 벤치마크가 시작되기 전에 상태 Object를 설정하기 위해 해당 메서드를 호출되어야 함을 JMH에게 알려준다.

     

    @TearDown 은 벤치마크가 실행된 후 상태 Object를 정리하기 위해 이 메서드를 호출해야 함을 JMH에게 알려준다.

     

    📍 @Setup, @TearDown가 적용된 Method의 실행시간은 벤치마크 시간에 포함되지 않는다 ❌

     

    📍 @Setup / @TearDown은 Level Argument의 설정이 가능하다

    • 메소드를 언제 호출해야 할지를 JMH에게 알려준다.
    Level Description
    Trial 벤치마크를 실행할 때 마다 한번씩 호출
    Iteration 벤치마크를 반복할 때마다 한번씩 호출
    Invocation 벤치마크 메소드를 호출할 때마다 호출

     

    @State(Scope.Thread)
    public static class MyState {
    
        @Setup(Level.Trial)
        public void doSetup() {
            sum = 0;
            System.out.println("Do Setup");
        }
    
        @TearDown(Level.Trial)
        public void doTearDown() {
            System.out.println("Do TearDown");
        }
    
        public int a = 1;
        public int b = 2;
        public int sum ;
    }
    

     

    🕳️ Black Hole

    JMH는 벤치마크 코드에서 계산된 값을 반환하지 않아도 JVM에서 실제로 사용하고 있다고 속이는 Blackhole 방법을 제공한다.

    public class MyBenchmark {
    
       @Benchmark
       public void testMethod(Blackhole blackhole) {
            int a = 1;
            int b = 2;
            int sum = a + b;
            blackhole.consume(sum);
        }
    }

    JMH가 제공하는 Blackhole 클래스를 파라미터로 받고 해당 코드를 수행할 시

    - Method에서 계산된 값을 넘겨주면 JVM에서는 계산된 값을 사용한 것으로 인식해 정확한 측정이 가능하다.

    - method내 여러 계산된 값이 도출된다면 지속적으로 consume()를 호출하여 값을 전달하면 된다.

     

    🧑🏻‍💻 설치 및 실행


    📍 gradle project에서 JMH 라이브러리를 사용했습니다.

     

    🐘 build.gradle

    📍 plugin 추가

    plugins {
    	id 'me.champeau.jmh' version '0.6.6'
    }
    

     

    📍 dependencies 에서 버전 변경하기

    dependencies {
        jmh 'org.openjdk.jmh:jmh-core:0.9'
        jmh 'org.openjdk.jmh:jmh-generator-annprocess:0.9'
    }
    
    • 버전을 변경하는 것으로 버전을 업그레이드 할 수 있다.

     

    📁 src/jmh 폴더 생성

    • 벤치마크 소스 파일은 src/jmh 에서 찾을 수 있다.
    src/jmh
         |- java       : java sources for benchmarks
         |- resources  : resources for benchmarks
    

     

    ⌨️ 실행

    @State(Scope.Benchmark)
    public class Sample {
    
        private static final int N = 1000;
    
        @Benchmark
        public void testRandom(Blackhole blackhole) {
            Random random = new Random();
            for (int i = 0; i < N; i++) {
                int result = random.nextInt();
                blackhole.consume(result);
            }
        }
    
        @Benchmark
        public void testThreadLocalRandom(Blackhole blackhole) {
            for (int i = 0; i < N; i++) {
                int result = ThreadLocalRandom.current().nextInt();
                blackhole.consume(result);
            }
        }
    }
    

     

    gradle을 이용해 빌드

    ./gradlew jmh
    

     

    /build/results/jmh/results.txt 에서 결과를 확인할 수 있다.

    Benchmark                      Mode  Cnt       Score   Error  Units
    Sample.testRandom             thrpt       105661.179          ops/s
    Sample.testThreadLocalRandom  thrpt       294349.298          ops/s
    

     

    참고자료

    Microbenchmarking with Java | Baeldung

    GitHub - openjdk/jmh: https://openjdk.org/projects/code-tools/jmh

    GitHub - melix/jmh-gradle-plugin: Integrates the JMH benchmarking framework with Gradle

    JMH: Java Benchmark Tool

    How to Do Benchmarking in Java?

    JMH - Java Microbenchmark Harness

    JMH(Java Microbenchmark Harness) 사용법

    '☕️ java' 카테고리의 다른 글

    👀 Unit Test 네이밍 컨벤션  (0) 2023.10.24
    일급 컬렉션  (1) 2023.10.24
    🆚 Random 함수 비교  (0) 2023.10.23
    DAO DTO / VO  (0) 2023.03.10
    [Java] 실수 표현 - 고정소수점, 부동소수점  (0) 2023.03.07

    댓글

Designed by Tistory.