SERVER/Spring Boot

[Spring Boot] JMH - Benchmark Test ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ ์ธก์ •ํ•˜๊ธฐ

GaGah 2023. 2. 15. 23:10

๐Ÿ”ต JMH(Java MicroBenchmark Harness) ๋ž€?

  • Open JDK์—์„œ ๊ฐœ๋ฐœํ•œ ์„ฑ๋Šฅ ์ธก์ • ํˆด
  • ํŠน์ • ๋ฉ”์†Œ๋“œ์˜ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉ

 

๐Ÿ”ต  JMH ๋Š” ์–ธ์ œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ?

์„ฑ๋Šฅ ํŠœ๋‹์„ ํ•  ๋•Œ, ๋ฉ”์†Œ๋“œ์˜ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•˜์—ฌ ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

์—ฌ๋Ÿฌ๊ฐ€์ง€ ์กฐ๊ฑด ๋ฐ ๋ถ€ํ•˜๋ฅผ ์ฃผ๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, JMH๋ฅผ ํ†ตํ•ด์„œ๋Š” ๋ฉ”์†Œ๋“œ์˜ ์„ฑ๋Šฅ ํŠœ๋‹์„ ๋กœ์ปฌ์—์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‹คํ–‰ํ•ด๋ณด๊ณ  ์ง„ํ–‰ํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ์ข‹๋‹ค.

 

 

๐Ÿ”ต  JMH ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์†Œ๋“œ์˜ ์„ฑ๋Šฅ ์ธก์ •ํ•˜๊ธฐ

1) pom.xml ์— jmh-core ์™€ jmh-generator-annprocess ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€

pom.xml
    <!-- jmh -->
    <dependency>
      <groupId>org.openjdk.jmh</groupId>
      <artifactId>jmh-core</artifactId>
      <version>1.26</version>
    </dependency>
    <dependency>
      <groupId>org.openjdk.jmh</groupId>
      <artifactId>jmh-generator-annprocess</artifactId>
      <version>1.26</version>
    </dependency>

 

2) Benchmark ์šฉ ํด๋ž˜์Šค ์ƒ์„ฑ

BenchmarkTest class ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ณ , ์‹ค์ œ๋กœ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์€ ๋Œ€์ƒ์˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ค€๋‹ค.

public class BenchmarkTest {

    @Benchmark
    public void benchmark(BenchmarkState state, Blackhole bh) {
    	// ์‹ค์ œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์€ ์ฝ”๋“œ ์ž‘์„ฑ
        List<Integer> list = state.list;
        for (int i = 0; i < 1000; i++)
            bh.consume (list.get (i));
    }

 

3) ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ฆด jmh ์†์„ฑ๋“ค์„ ์…‹ํŒ…

์†์„ฑ์€ properties๋กœ ๋นผ์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ์ฝ”๋“œ ์ƒ์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

์†์„ฑ๋“ค์— ๋Œ€ํ•œ ์ฝ”๋ฉ˜ํŠธ๋Š” ์•„๋ž˜์—์„œ ํ™•์ธํ•ด์ฃผ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.
public class BenchmarkTest {

	@Test
    public void launchBenchmark() throws Exception {
        Options opt = new OptionsBuilder()
            .include(this.getClass().getName() + ".*")
            .mode(Mode.AverageTime)
            .timeUnit(TimeUnit.MICROSECONDS) // ์„ฑ๋Šฅ ๊ฒฐ๊ณผ์˜ ์‹œ๊ฐ„ ๋‹จ์œ„ 
            .warmupTime(TimeValue.seconds(1))
            .warmupIterations(2) // ์‚ฌ์ „ ํ…Œ์ŠคํŠธ ํšŸ์ˆ˜
            .measurementTime(TimeValue.seconds(1)) // ํ…Œ์ŠคํŠธ ๋‹น ์ธก์ • ์‹œ๊ฐ„
            .measurementIterations(2) // ํ…Œ์ŠคํŠธ ๋ฐ˜๋ณตํšŸ์ˆ˜
            .threads(2) // ์“ฐ๋ ˆ๋“œ
            .forks(1)
            .shouldFailOnError(true)
            .shouldDoGC(true)
            .build();

        new Runner(opt).run();
    }

    @Benchmark
    public void benchmark(BenchmarkState state, Blackhole bh) {
    	// ์‹ค์ œ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์€ ์ฝ”๋“œ ์ž‘์„ฑ
        List<Integer> list = state.list;
        for (int i = 0; i < 1000; i++)
            bh.consume (list.get (i));
    }

 

4) @State๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ƒํƒœ๋ฅผ ์ง€์ • ๋ฐ  @Setup ์„ ํ†ตํ•ด ์‚ฌ์ „ ์ž‘์—…์„ ์ง„ํ–‰

(1) Scope.Thread 

      ์“ฐ๋ ˆ๋“œ๋ณ„๋กœ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

(2) Scope.Benchmark

      ๋™์ผํ•œ ํ…Œ์ŠคํŠธ ๋‚ด์˜ ๋ชจ๋“  ์“ฐ๋ ˆ๋“œ์—์„œ ๋™์ผํ•œ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ณต์œ ํ•œ๋‹ค. (๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉ)

(3) Scope.Group 

       ์“ฐ๋ ˆ๋“œ ๊ทธ๋ฃน๋งˆ๋‹ค ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

@State(Scope.Thread)
public static class BenchmarkState {
	List<Integer> list;
    
    @Setup(Level.Trial)
    public void init() {
    	Random rand = new Random();
        
        list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
        	list.add (rand.nextInt());
		}
	}
}

 

์‹คํ–‰ ๊ฒฐ๊ณผ

# Run progress: 0.00% complete, ETA 00:00:04
# Fork: 1 of 1
# Warmup Iteration   1: 2.744 us/op
# Warmup Iteration   2: 2.688 us/op
Iteration   1: 2.466 us/op
Iteration   2: 2.453 us/op

 


 

๐Ÿ”ต Trouble Shooting 

์—๋Ÿฌ 

java.lang.RuntimeException: ERROR: Unable to find the resource: /META-INF/BenchmarkList

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• : pom.xml ์ˆ˜์ •

์•„๋ž˜ ๋ฌธ์„œ ์ฐธ๊ณ ํ•˜์—ฌ ํ•ด๊ฒฐ 
https://howtodoinjava.com/java/exception-handling/jmh-error-unable-to-find-the-resource-meta-inf-benchmarklist/
  // 1. properties ์•ˆ์— jmh.version ์ถ”๊ฐ€
  <properties>
   ...(์ค‘๋žต)
    <jmh.version>1.26</jmh.version>
  </properties>



// 2. path ์ถ”๊ฐ€ > jmh
<plugin>
 ...(์ค‘๋žต)
        <configuration>
          <annotationProcessorPaths>
            <path>
              <groupId>org.openjdk.jmh</groupId>
              <artifactId>jmh-generator-annprocess</artifactId>
              <version>${jmh.version}</version>
            </path>
          </annotationProcessorPaths>
        </configuration>
</plugin>

 

 

 

๐ŸŸฃ jmh์˜ ์†์„ฑ

jmh {
   includes = ['some regular expression'] // include pattern (regular expression) for benchmarks to be executed
   excludes = ['some regular expression'] // exclude pattern (regular expression) for benchmarks to be executed
   iterations = 10 // Number of measurement iterations to do.
   benchmarkMode = ['thrpt','ss'] // Benchmark mode. Available modes are: [Throughput/thrpt, AverageTime/avgt, SampleTime/sample, SingleShotTime/ss, All/all]
   batchSize = 1 // Batch size: number of benchmark method calls per operation. (some benchmark modes can ignore this setting)
   fork = 2 // How many times to forks a single benchmark. Use 0 to disable forking altogether
   failOnError = false // Should JMH fail immediately if any benchmark had experienced the unrecoverable error?
   forceGC = false // Should JMH force GC between iterations?
   jvm = 'myjvm' // Custom JVM to use when forking.
   jvmArgs = ['Custom JVM args to use when forking.']
   jvmArgsAppend = ['Custom JVM args to use when forking (append these)']
   jvmArgsPrepend =[ 'Custom JVM args to use when forking (prepend these)']
   humanOutputFile = project.file("${project.buildDir}/results/jmh/human.txt") // human-readable output file
   resultsFile = project.file("${project.buildDir}/results/jmh/results.txt") // results file
   operationsPerInvocation = 10 // Operations per invocation.
   benchmarkParameters =  [:] // Benchmark parameters.
   profilers = [] // Use profilers to collect additional data. Supported profilers: [cl, comp, gc, stack, perf, perfnorm, perfasm, xperf, xperfasm, hs_cl, hs_comp, hs_gc, hs_rt, hs_thr, async]
   timeOnIteration = '1s' // Time to spend at each measurement iteration.
   resultFormat = 'CSV' // Result format type (one of CSV, JSON, NONE, SCSV, TEXT)
   synchronizeIterations = false // Synchronize iterations?
   threads = 4 // Number of worker threads to run with.
   threadGroups = [2,3,4] //Override thread group distribution for asymmetric benchmarks.
   timeout = '1s' // Timeout for benchmark iteration.
   timeUnit = 'ms' // Output time unit. Available time units are: [m, s, ms, us, ns].
   verbosity = 'NORMAL' // Verbosity mode. Available modes are: [SILENT, NORMAL, EXTRA]
   warmup = '1s' // Time to spend at each warmup iteration.
   warmupBatchSize = 10 // Warmup batch size: number of benchmark method calls per operation.
   warmupForks = 0 // How many warmup forks to make for a single benchmark. 0 to disable warmup forks.
   warmupIterations = 1 // Number of warmup iterations to do.
   warmupMode = 'INDI' // Warmup mode for warming up selected benchmarks. Warmup modes are: [INDI, BULK, BULK_INDI].
   warmupBenchmarks = ['.*Warmup'] // Warmup benchmarks to include in the run in addition to already selected. JMH will not measure these benchmarks, but only use them for the warmup.

   zip64 = true // Use ZIP64 format for bigger archives
   jmhVersion = '1.35' // Specifies JMH version
   includeTests = true // Allows to include test sources into generate JMH jar, i.e. use it when benchmarks depend on the test classes.
   duplicateClassesStrategy = DuplicatesStrategy.FAIL // Strategy to apply when encountring duplicate classes during creation of the fat jar (i.e. while executing jmhJar task)
}

 

 

 

๐Ÿ”ด JMH Benchmark Test์˜ ํ•œ๊ณ„์ 

  • ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•œ CPU ์‚ฌ์šฉ๋ฅ , ๊ฐœ์ธ PC ์„ฑ๋Šฅ์— ๋”ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ’์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ •ํ™•ํ•œ ๋ฐ์ดํ„ฐ์˜ ์ง€ํ‘œ๋กœ ํŒ๋‹ฌํ•  ์ˆ˜ ์—†์Œ
  • ์ฝ”๋“œ๊ฐ€ ๋™์ผํ•˜๋‹ค๊ณ  ๋‹ค๋ฅธ ์‚ฌ์–‘์˜ PC์—์„œ ์ง„ํ–‰ํ•˜๋ฉด ์ธก์ •ํ•˜๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•œ ๋ฉ”์†Œ๋“œ ์„ฑ๋Šฅ ์ธก์ •์— ์‚ฌ์šฉํ•ด์•ผ ํ•จ.

 

๐Ÿ”ต  ์ฐธ๊ณ  ๋ฌธ์„œ

1) github

2) baeldung

3) openJDK ๊ณต์‹๋ฌธ์„œ

 

OpenJDK: jmh

Code Tools: jmh JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks written in Java and other languages targetting the JVM. Links

openjdk.org

4) ์˜ค๋ฅ˜ ์ฐธ๊ณ 

 

How to run JMH from inside JUnit tests?

How can I run JMH benchmarks inside my existing project using JUnit tests? The official documentation recommends making a separate project, using Maven shade plugin, and launching JMH inside the main

stackoverflow.com

LIST