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