[Spring Boot] JMH - Benchmark Test μ¬μ©νμ¬ μ±λ₯ μΈ‘μ νκΈ°
π΅ 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 곡μλ¬Έμ
4) μ€λ₯ μ°Έκ³