【JMH】Kotlin-DSLでJMH Gradle Pluginを用いたベンチマークを動かすまで【Kotlin】

`me.champeau.gradle.jmh 0.5.2`を利用する場合、`Windows`では以下の問題が発生すると思われます。
再ビルドが通らない、ベンチマークを中断してもプロセスが死なないなどの問題が発生する場合`gradle jmh --no-daemon`として実行して下さい。

[【JMH】JMH Gradle PluginはWindows 10で正常に動作しない【Gradle】 \- Qiita](https://qiita.com/wrongwrong/items/78b7a8d72b7693650c05)

---
`JMH Gradle Plugin`（`me.champeau.gradle.jmh`）を用いて`JMH`ベンチマークを動かすサンプルを紹介します。
プロジェクト全体は以下のリポジトリに上げてあります。

- [k163377/jmh\-gradle\-kotlin\-sample](https://github.com/k163377/jmh-gradle-kotlin-sample)

# プロジェクトの初期化
プロジェクトは、`Intellij IDEA`を用いて以下のように初期化しました。

**1. `Kotlin DSL build script`と`Kotlin/JVM`にチェックを付け、それ以外は外す**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/bda8cf1f-85d5-0a0f-8e24-d6ef505570cd.png)
**2. `Name`と`GroupId`を適当に設定する**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/14251532-1619-360d-f84e-86fd47c1988b.png)
以下は初期化直後の`build.gradle.kts`です。

```kotlin:build.gradle.kts（変更前）
plugins {
    kotlin("jvm") version "1.4.10"
}

group = "com.wrongwrong"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))
}
```

# build.gradle.ktsへの追記
プラグインとライブラリをそれぞれ追加します。

```diff
plugins {
    kotlin("jvm") version "1.4.10"
+   id("me.champeau.gradle.jmh") version "0.5.2"
}

group = "com.wrongwrong"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))
+   implementation(group = "org.openjdk.jmh", name = "jmh-core", version = "1.25.2")
}
```

# src/jmhに`groupId`に合わせたパッケージを用意する
[プラグインのREADMEに記載されている](https://github.com/melix/jmh-gradle-plugin#configuration)通り、`JMH Gradle Plugin`では`src/jmh`配下にベンチマークが有ることを想定しています。
また、[`groupId`に合わせたパッケージでなければベンチマークが正常に機能しません](https://qiita.com/wrongwrong/items/ba562fcf561732f7a0d7)。

よって、今回は`group = "com.wrongwrong"`としているため、以下のようなディレクトリ構成をする必要が有ります[^hosoku]。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/957b1994-3e94-eaaf-5465-8d2a3acd2af9.png)

ここまで設定をやった上でベンチマークプログラムを作れば、`gradle jmh`で実行できます。

[^hosoku]: 設定等を弄ればこの限りではないと思いますが、今回は簡単のため触れません。

# ベンチマークの追加と実行
簡単なベンチマークとして、ランダムに生成した2つの`BigDecimal`を足し合わせるベンチマークを行ってみます。
前節で説明した通り、パッケージは`com.wrongwrong`として配置します。

```kotlin
package com.wrongwrong

import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State
import java.math.BigDecimal
import java.util.concurrent.ThreadLocalRandom

// JMHのベンチマーク関連の内容は`open class`として定義する必要が有る
open class SampleBenchmark {
    @State(Scope.Thread)
    open class Input {
        val a: BigDecimal = ThreadLocalRandom.current().nextDouble(MAX_VALUE).toBigDecimal()
        val b: BigDecimal = ThreadLocalRandom.current().nextDouble(MAX_VALUE).toBigDecimal()

        companion object {
            private const val MAX_VALUE = 10000.0
        }
    }

    @Benchmark
    fun measureAdd(input: Input): BigDecimal = input.a + input.b
}
```

## 実行結果
`gradle jmh --no-daemon`として実行した場合の実行結果は以下のようになります（個人情報が絡む部分は`...`で省略しています）。

```bash
>gradle jmh --no-daemon

> Task :jmhRunBytecodeGenerator

...

# Warmup Iteration   1: UTING [5s]
> Task :jmh
# JMH version: 1.25
# VM version: JDK 1.8.0_241, Java HotSpot(TM) 64-Bit Server VM, 25.241-b07
# VM invoker: ...
# VM options: <none>
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.wrongwrong.SampleBenchmark.measureAdd

# Run progress: 0.00% complete, ETA 00:08:20
# Fork: 1 of 5

123535010.934 ops/s
# Warmup Iteration   2: 123979727.401 ops/s
# Warmup Iteration   3: 134043974.533 ops/s
# Warmup Iteration   4: 132468842.044 ops/s
# Warmup Iteration   5: 133974102.654 ops/s
Iteration   1: 132268760.988 ops/s]
Iteration   2: 134172199.789 ops/s5s]
Iteration   3: 132371282.057 ops/s15s]
Iteration   4: 134146688.977 ops/s25s]
Iteration   5: 132281181.244 ops/s35s]

> Task :jmh

# Run progress: 20.00% complete, ETA 00:06:41
# Fork: 2 of 5

# Warmup Iteration   1: 123418709.334 ops/s
# Warmup Iteration   2: 123974249.446 ops/s
# Warmup Iteration   3: 133771169.311 ops/s
# Warmup Iteration   4: 132314587.092 ops/s
# Warmup Iteration   5: 133683533.888 ops/s
Iteration   1: 132386673.995 ops/s35s]
Iteration   2: 133889633.018 ops/s45s]
Iteration   3: 132351518.216 ops/s55s]
Iteration   4: 133984395.968 ops/s5s]
Iteration   5: 132359193.064 ops/s15s]

> Task :jmh

# Run progress: 40.00% complete, ETA 00:05:00
# Fork: 3 of 5

# Warmup Iteration   1: 121795245.919 ops/s
# Warmup Iteration   2: 122968957.404 ops/s
# Warmup Iteration   3: 120593363.112 ops/s
# Warmup Iteration   4: 119424298.673 ops/s
# Warmup Iteration   5: 120837849.959 ops/s
Iteration   1: 119527380.883 ops/s16s]
Iteration   2: 120882687.677 ops/s26s]
Iteration   3: 119529815.116 ops/s36s]
Iteration   4: 121002476.444 ops/s46s]
Iteration   5: 119398499.673 ops/s56s]

> Task :jmh

# Run progress: 60.00% complete, ETA 00:03:20
# Fork: 4 of 5

# Warmup Iteration   1: 123466577.825 ops/s
# Warmup Iteration   2: 123937997.676 ops/s
# Warmup Iteration   3: 134091612.535 ops/s
# Warmup Iteration   4: 132087687.539 ops/s
# Warmup Iteration   5: 134119188.708 ops/s
Iteration   1: 132480152.938 ops/s56s]
Iteration   2: 133977021.297 ops/s6s]
Iteration   3: 132406987.113 ops/s16s]
Iteration   4: 134015243.441 ops/s26s]
Iteration   5: 132330706.552 ops/s36s]

> Task :jmh

# Run progress: 80.00% complete, ETA 00:01:40
# Fork: 5 of 5

# Warmup Iteration   1: 123306007.683 ops/s
# Warmup Iteration   2: 123730067.037 ops/s
# Warmup Iteration   3: 134053833.907 ops/s
# Warmup Iteration   4: 132625906.692 ops/s
# Warmup Iteration   5: 133897507.424 ops/s
Iteration   1: 132634681.882 ops/s36s]
Iteration   2: 134281816.570 ops/s46s]
Iteration   3: 132618358.352 ops/s56s]
Iteration   4: 134060049.422 ops/s6s]
Iteration   5: 132324371.611 ops/s16s]

> Task :jmh


Result "com.wrongwrong.SampleBenchmark.measureAdd":
  130467271.051 ±(99.9%) 4022370.483 ops/s [Average]
  (min, avg, max) = (119398499.673, 130467271.051, 134281816.570), stdev = 5369749.514
  CI (99.9%): [126444900.569, 134489641.534] (assumes normal distribution)


# Run complete. Total time: 00:08:21

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                    Mode  Cnt          Score         Error  Units
SampleBenchmark.measureAdd  thrpt   25  130467271.051 ± 4022370.483  ops/s

Benchmark result is saved to ...
```