JMH

JMH(Java Microbenchmark Harness)๋Š” Java ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋งˆ์ดํฌ๋กœ ๋ฒค์น˜๋งˆํฌ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” JVM ์ตœ์ ํ™”(JIT, Dead Code Elimination ๋“ฑ) ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ , ์ •ํ™•ํ•˜๊ณ  ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

JMH๋Š” JVM ์›Œ๋ฐ์—…, ์Šค๋ ˆ๋”ฉ ์˜ต์…˜, ์„ฑ๋Šฅ ์ธก์ • ๋ฐ˜๋ณต ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ๋ฒค์น˜๋งˆํ‚น ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ํ”ํ•œ ์‹ค์ˆ˜๋ฅผ ์˜ˆ๋ฐฉํ•ฉ๋‹ˆ๋‹ค.


ํŠน์ง•

  • JIT ์ตœ์ ํ™” ๋ฐฉ์ง€: JVM์˜ ํ•ซ์ŠคํŒŸ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ฒค์น˜๋งˆํฌ ๊ฒฐ๊ณผ๋ฅผ ์™œ๊ณกํ•˜์ง€ ์•Š๋„๋ก ๊ด€๋ฆฌ.

  • ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ ์ง€์›: ๋™์‹œ์„ฑ ํ…Œ์ŠคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ์„ค์ • ๊ฐ€๋Šฅ.

  • ์ •ํ™•ํ•œ ์„ฑ๋Šฅ ์ธก์ •: Warm-up, Iteration, Fork ์„ค์ •์„ ํ†ตํ•œ ์•ˆ์ •์ ์ธ ๊ฒฐ๊ณผ ์ œ๊ณต.

  • ์‚ฌ์šฉ ํŽธ๋ฆฌ์„ฑ: ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ์„ค์ •์œผ๋กœ ์‰ฝ๊ฒŒ ์ž‘์„ฑ ๊ฐ€๋Šฅ.


์„ค์ • ๋ฐฉ๋ฒ•

Maven ํ”„๋กœ์ ํŠธ์— JMH ์ถ”๊ฐ€

<dependencies>
    <dependency>
        <groupId>org.openjdk.jmh</groupId>
        <artifactId>jmh-core</artifactId>
        <version>1.38</version> <!-- ์ตœ์‹  ๋ฒ„์ „ ํ™•์ธ ํ•„์š” -->
    </dependency>
    <dependency>
        <groupId>org.openjdk.jmh</groupId>
        <artifactId>jmh-generator-annprocess</artifactId>
        <version>1.38</version>
    </dependency>
</dependencies>

Gradle ํ”„๋กœ์ ํŠธ์— JMH ์ถ”๊ฐ€

plugins {
    id 'java'
    id 'me.champeau.jmh' version '0.7.1' // JMH ํ”Œ๋Ÿฌ๊ทธ์ธ ์‚ฌ์šฉ
}

dependencies {
    implementation 'org.openjdk.jmh:jmh-core:1.38'
    annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.38'
}

JMH ์‚ฌ์šฉ๋ฒ•

import org.openjdk.jmh.annotations.*;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput) // 1์ดˆ๋‹น ์‹คํ–‰ ํšŸ์ˆ˜ ์ธก์ •
@OutputTimeUnit(TimeUnit.MILLISECONDS) // ๊ฒฐ๊ณผ๋ฅผ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ ์ถœ๋ ฅ
@State(Scope.Thread) // ์ƒํƒœ๋ฅผ ๊ฐ ์Šค๋ ˆ๋“œ์—์„œ ๋…๋ฆฝ์ ์œผ๋กœ ์œ ์ง€
public class MyBenchmark {

    private int[] numbers;

    @Setup(Level.Iteration) // ๊ฐ ๋ฐ˜๋ณต ์ „ ์ดˆ๊ธฐํ™”
    public void setUp() {
        numbers = new int[1_000];
        for (int i = 0; i < numbers.length; i++) {
            numbers[i] = i;
        }
    }

    @Benchmark
    public int sumArray() {
        int sum = 0;
        for (int num : numbers) {
            sum += num;
        }
        return sum;
    }

    @Benchmark
    public int sumArrayUsingStreams() {
        return java.util.Arrays.stream(numbers).sum();
    }
}
  1. mvn clean install ๋˜๋Š” ./gradlew jmhJar๋กœ ๋นŒ๋“œ.

  2. ์‹คํ–‰:

    java -jar target/benchmarks.jar

    ๋˜๋Š”

    java -jar build/libs/benchmarks.jar

์ฃผ์š” ์–ด๋…ธํ…Œ์ด์…˜ ๋ฐ ์„ค์ •

  • @Benchmark: ๋ฒค์น˜๋งˆํฌ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ ํ‘œ์‹œ.

  • @BenchmarkMode: ๋ฒค์น˜๋งˆํฌ ์ธก์ • ๋ชจ๋“œ ์„ค์ • (์˜ˆ: Throughput, AverageTime, SampleTime, AllModes ๋“ฑ).

  • @OutputTimeUnit: ๊ฒฐ๊ณผ์˜ ์‹œ๊ฐ„ ๋‹จ์œ„ ์„ค์ •.

  • @State: ๋ฒค์น˜๋งˆํฌ ์‹คํ–‰ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด ์Šค์ฝ”ํ”„ ์„ค์ • (Thread, Group, Singleton).

  • @Setup / @TearDown: ๋ฒค์น˜๋งˆํฌ ์ค€๋น„ ๋ฐ ์ •๋ฆฌ ์ž‘์—… ์ง€์ •.

  • @Param: ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋‹ค์–‘ํ•œ ์ž…๋ ฅ๊ฐ’ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ.


6. JMH ์‹คํ–‰ ์‹œ ์ฃผ์š” ์„ค์ •

  • Fork: JVM ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ช‡ ๋ฒˆ ์žฌ์‹œ์ž‘ํ•˜์—ฌ ๋ฒค์น˜๋งˆํฌ ์‹คํ–‰ํ• ์ง€ ๊ฒฐ์ •.

    @Fork(2) // ๋‘ ๋ฒˆ์˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰
  • Warmup: JVM ์›Œ๋ฐ์—…์„ ๋ช‡ ๋ฒˆ ๋ฐ˜๋ณตํ• ์ง€ ์„ค์ •.

    @Warmup(iterations = 5) // 5ํšŒ ์›Œ๋ฐ์—…
  • Measurement: ์‹ค์ œ ๋ฒค์น˜๋งˆํฌ ๋ฐ˜๋ณต ํšŸ์ˆ˜.

    @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)

Last updated