feat(benchmarks): add JMH benchmarks for performance testing and load scripts
Build & Test (NowChessSystems) TeamCity build failed
Build & Test (NowChessSystems) TeamCity build failed
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# Gradle
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
bin/
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# NetBeans
|
||||
nb-configuration.xml
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
.factorypath
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# patch
|
||||
*.orig
|
||||
*.rej
|
||||
|
||||
# Local environment
|
||||
.env
|
||||
@@ -0,0 +1,77 @@
|
||||
# JMH Benchmarks
|
||||
|
||||
Java Microbenchmark Harness (JMH) benchmarks for performance-critical components.
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
./gradlew modules:benchmarks:jmhJar
|
||||
```
|
||||
|
||||
Produces `modules/benchmarks/build/libs/benchmarks-1.0-SNAPSHOT-jmh.jar`.
|
||||
|
||||
## Running Benchmarks
|
||||
|
||||
Since these benchmarks are written in Scala, JMH auto-discovery via jar packaging doesn't work. Instead, run benchmarks via classpath:
|
||||
|
||||
```bash
|
||||
./gradlew modules:benchmarks:run --args='de.nowchess.benchmarks.MoveBenchmark'
|
||||
```
|
||||
|
||||
Or compile and run with explicit classpath:
|
||||
```bash
|
||||
./gradlew modules:benchmarks:compileScala
|
||||
java -cp "modules/benchmarks/build/classes/scala/main:modules/benchmarks/build/resources/main:$(./gradlew -q printClasspath)" \
|
||||
org.openjdk.jmh.Main de.nowchess.benchmarks.MoveBenchmark
|
||||
```
|
||||
|
||||
Adjust JMH options:
|
||||
- `-f 1` — 1 fork (faster iteration, less reliable)
|
||||
- `-i 3 -w 2` — 3 measurement iterations, 2 warmup iterations
|
||||
- `-bm avgt` — average time mode
|
||||
- `-tu us` — time unit (microseconds)
|
||||
|
||||
Example:
|
||||
```bash
|
||||
java -cp "..." org.openjdk.jmh.Main de.nowchess.benchmarks.MoveBenchmark -f 1 -i 3 -w 2 -bm avgt
|
||||
```
|
||||
|
||||
## Writing Benchmarks
|
||||
|
||||
Create Scala class with `@Benchmark` methods:
|
||||
|
||||
```scala
|
||||
@BenchmarkMode(Array(Mode.AverageTime, Mode.Throughput))
|
||||
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
@Fork(2)
|
||||
@Warmup(iterations = 3)
|
||||
@Measurement(iterations = 5)
|
||||
class SomeBenchmark {
|
||||
@Setup(Level.Trial)
|
||||
def setup(): Unit = ???
|
||||
|
||||
@org.openjdk.jmh.annotations.Benchmark
|
||||
def benchmarkSomething(bh: Blackhole): Unit = {
|
||||
val result = expensiveOperation()
|
||||
bh.consume(result) // Prevent dead-code elimination
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Benchmark Modes
|
||||
|
||||
- `AverageTime` — average execution time per operation
|
||||
- `Throughput` — operations per unit time
|
||||
- `SampleTime` — latency samples (min, max, percentiles)
|
||||
- `SingleShotTime` — total time to execute N ops once
|
||||
- `All` — run all modes
|
||||
|
||||
## Limitations
|
||||
|
||||
JMH's Gradle plugin doesn't fully support Scala annotation processing, so benchmarks must be run explicitly by class name rather than auto-discovered. This is a known limitation of JMH + Scala; consider Java-based benchmarks for full auto-discovery support.
|
||||
|
||||
## References
|
||||
|
||||
- [OpenJDK JMH](https://github.com/openjdk/jmh)
|
||||
- [JMH Samples](https://hg.openjdk.org/code-tools/jmh/file/tip/jmh-samples)
|
||||
@@ -0,0 +1,61 @@
|
||||
plugins {
|
||||
id("scala")
|
||||
id("me.champeau.jmh")
|
||||
}
|
||||
|
||||
group = "de.nowchess"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
scala {
|
||||
scalaVersion = versions["SCALA3"]!!
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.scala-lang:scala3-library_3") {
|
||||
version {
|
||||
strictly(versions["SCALA3"]!!)
|
||||
}
|
||||
}
|
||||
compileOnly("org.scala-lang:scala3-compiler_3") {
|
||||
version {
|
||||
strictly(versions["SCALA3"]!!)
|
||||
}
|
||||
}
|
||||
|
||||
// Core modules for benchmarking
|
||||
implementation(project(":modules:api"))
|
||||
implementation(project(":modules:core"))
|
||||
implementation(project(":modules:rule"))
|
||||
implementation(project(":modules:bot-platform"))
|
||||
|
||||
// JMH (jmh configuration is for annotation processor + runtime)
|
||||
implementation("org.openjdk.jmh:jmh-core:${versions["JMH"]!!}")
|
||||
jmh("org.openjdk.jmh:jmh-core:${versions["JMH"]!!}")
|
||||
jmh("org.openjdk.jmh:jmh-generator-annprocess:${versions["JMH"]!!}")
|
||||
}
|
||||
|
||||
configurations.matching { !it.name.startsWith("jmh") }.configureEach {
|
||||
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
||||
}
|
||||
|
||||
jmh {
|
||||
jmhVersion.set(versions["JMH"]!!)
|
||||
includeTests.set(false)
|
||||
duplicateClassesStrategy.set(DuplicatesStrategy.WARN)
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
|
||||
tasks.withType<org.gradle.api.tasks.scala.ScalaCompile> {
|
||||
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.nowchess.benchmarks
|
||||
|
||||
import org.openjdk.jmh.annotations.{BenchmarkMode, Fork, Measurement, OutputTimeUnit, Setup, State, Warmup}
|
||||
import org.openjdk.jmh.annotations.Mode
|
||||
import org.openjdk.jmh.annotations.Scope
|
||||
import org.openjdk.jmh.annotations.Level
|
||||
import org.openjdk.jmh.infra.Blackhole
|
||||
import de.nowchess.api.board.{File, Rank, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
// scalafix:off DisableSyntax.var
|
||||
@BenchmarkMode(Array(Mode.AverageTime, Mode.Throughput))
|
||||
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
@Fork(value = 2, warmups = 1)
|
||||
@Warmup(iterations = 3, time = 1)
|
||||
@Measurement(iterations = 5, time = 1)
|
||||
class MoveBenchmark {
|
||||
private var gameContext: GameContext = scala.compiletime.uninitialized
|
||||
|
||||
@Setup(Level.Trial)
|
||||
def setup(): Unit =
|
||||
gameContext = GameContext.initial
|
||||
|
||||
@org.openjdk.jmh.annotations.Benchmark
|
||||
def benchmarkAllLegalMoves(bh: Blackhole): Unit = {
|
||||
val moves = DefaultRules.allLegalMoves(gameContext)
|
||||
bh.consume(moves)
|
||||
}
|
||||
|
||||
@org.openjdk.jmh.annotations.Benchmark
|
||||
def benchmarkIsCheck(bh: Blackhole): Unit = {
|
||||
val inCheck = DefaultRules.isCheck(gameContext)
|
||||
bh.consume(inCheck)
|
||||
}
|
||||
|
||||
@org.openjdk.jmh.annotations.Benchmark
|
||||
def benchmarkLegalMovesFirstSquare(bh: Blackhole): Unit = {
|
||||
val firstSquare = Square(File.A, Rank.R2)
|
||||
val moves = DefaultRules.legalMoves(gameContext)(firstSquare)
|
||||
bh.consume(moves)
|
||||
}
|
||||
|
||||
@org.openjdk.jmh.annotations.Benchmark
|
||||
def benchmarkIsCheckmate(bh: Blackhole): Unit = {
|
||||
val result = DefaultRules.isCheckmate(gameContext)
|
||||
bh.consume(result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user