kotlinx-benchmark

repository·master·Indexed 20 days ago

https://github.com/kotlin/kotlinx-benchmark

A toolkit for running benchmarks for multiplatform code written in Kotlin, providing statistical analysis and detailed performance reports. It supports Kotlin/JVM, Kotlin/JS, Kotlin/Native, and experimental Kotlin/WasmJs and Kotlin/WasmWasi targets. The toolkit integrates with JMH for JVM targets and provides configuration options for iterations, warmups, and measurement modes across different platforms.

Tokens
14K
Snippets
50
Records
64
Agent score
70%

What's inside kotlinx-benchmark

  1. Overview of kotlinx-benchmark

    master

    kotlinx-benchmark is a toolkit designed for running benchmarks for multiplatform code written in Kotlin. It provides low-noise, reliable results, statistical analysis, and detailed performance reports. It supports the following targets:

    • Kotlin/JVM
    • Kotlin/JS
    • Kotlin/Native
    • Kotlin/WasmJs (experimental)
    • Kotlin/WasmWasi (experimental)
  2. How kotlinx-benchmark uses annotations

    master

    The kotlinx-benchmark library uses an annotation-based approach similar to the Java Microbenchmark Harness (JMH). Instead of writing manual timing logic, you decorate your benchmark classes and methods with specific annotations. The library extracts this metadata to generate the necessary execution code for the target platform. This design allows you to run JMH-style benchmarks written in Kotlin across multiple platforms with minimal modifications.

    import kotlinx.benchmark.*
    
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS)
    @Warmup(iterations = 10, time = 500, timeUnit = BenchmarkTimeUnit.MILLISECONDS)
    @Measurement(iterations = 20, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS)
    @State(Scope.Benchmark)
    class ExampleBenchmark {
        // ...
    }
  3. Understand the task generation pattern in kotlinx-benchmark

    master

    The kotlinx-benchmark Gradle plugin dynamically generates tasks based on your defined configuration profiles and targets.

    For every combination of a configuration profile and a registered target, a specific task is created to execute that profile on that platform.

    Task Naming Convention

    Task PatternDescription
    benchmarkRuns the main profile for all registered targets.
    <targetName>BenchmarkRuns the main profile for a specific target.
    <configName>BenchmarkRuns a custom profile for all registered targets.
    <targetName><configName>BenchmarkRuns a custom profile for a specific target.
    <targetName>BenchmarkJar(JVM only) Produces a self-contained executable JAR for the target.
    // Example of how configuration and targets drive task generation
    benchmark {
        configurations {
            named("main") { /* ... */ }
            register("smoke") { /* ... */ }
        }
    
        targets {
            register("jvm")
            register("js")
        }
    }
    
    // Resulting tasks include:
    // - benchmark (main on jvm + js)
    // - jvmBenchmark (main on jvm)
    // - smokeBenchmark (smoke on jvm + js)
    // - jvmSmokeBenchmark (smoke on jvm)
  4. Set up a Kotlin Multiplatform project for benchmarking

    master

    To use kotlinx-benchmark in a Kotlin Multiplatform (KMP) project, you must apply the plugin, configure repositories, and add the runtime dependency to your commonMain source set.

    Kotlin DSL Setup

    1. Apply the plugin in build.gradle.kts.
    2. Configure pluginManagement in settings.gradle.kts to include gradlePluginPortal().
    3. Add kotlinx-benchmark-runtime to the commonMain dependencies in build.gradle.kts.
    4. Add mavenCentral() to the repositories block in build.gradle.kts.
    // build.gradle.kts
    plugins {
        id("org.jetbrains.kotlinx.benchmark") version "0.4.17"
    }
    
    // settings.gradle.kts
    pluginManagement {
        repositories {
            gradlePluginPortal()
        }
    }
    
    // build.gradle.kts
    kotlin {
        sourceSets {
            commonMain {
                dependencies {
                    implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.4.17")
                }
            }
        }
    }
    
    repositories {
        mavenCentral()
    }
  5. Prerequisites for using kotlinx-benchmark

    master

    To use kotlinx-benchmark, ensure your environment meets the following requirements:

    • Kotlin Version: 2.2.0 or newer.
    • Gradle Version: Latest stable Gradle 8 release or newer.

    Note on Wasm targets: Because Kotlin/WasmJs is experimental, support is only guaranteed for the specific Kotlin version used to build the library (currently Kotlin 2.2.0).

  6. Generate a benchmark JAR for JVM targets

    master

    If you have registered a Kotlin/JVM target, the plugin provides a <targetName>BenchmarkJar task. This task produces a self-contained executable JAR file containing your benchmarks and the necessary JMH infrastructure.

    • Output Location: build/benchmarks/<targetName>/jars/
    • Usage: Run the JAR using the standard Java command line. This is particularly useful for running JMH profilers.

    In a project with a jvm target, the task jvmBenchmarkJar will create the file in build/benchmarks/jvm/jars/.

    # Run the generated JAR
    java -jar build/benchmarks/jvm/jars/your-benchmark-jar.jar
    
    # View available options
    java -jar build/benchmarks/jvm/jars/your-benchmark-jar.jar -h
  7. Define Benchmark Configuration Profiles

    master

    The configurations section within the benchmark block allows you to define custom execution profiles. Each profile creates a corresponding Gradle task (e.g., a profile named smoke creates the smokeBenchmark task). You can use these profiles to group benchmarks or apply specific settings to subsets of your tests.

    By default, a profile named main is provided.

    // build.gradle.kts
    benchmark {
        configurations {
            register("smoke") {
                // Configure this configuration profile here
            }
            // you can create additional profiles here
        }
    }
  8. Configure Kotlin/JS, Native, and Wasm benchmark targets

    master

    You can register specific targets for JS, Native, or Wasm.

    • Kotlin/JS: Requires a Node.js execution environment.
    • Kotlin/Native: Supports all targets supported by the Kotlin/Native compiler. Note that while you can register multiple native targets, benchmarks can only be executed for the host target.
    • Kotlin/Wasm: Uses the wasmJs target with a Node.js environment (experimental).
    // Kotlin/JS
    kotlin { js { nodejs() } }
    benchmark { targets { register("js") } }
    
    // Kotlin/Native (e.g., Linux X64)
    kotlin { linuxX64() }
    benchmark { targets { register("linuxX64") } }
    
    // Kotlin/Wasm
    kotlin { wasmJs { nodejs() } }
    benchmark { targets { register("wasmJs") } }
  9. Configure benchmark profiles and targets

    master

    Use the benchmark extension in your build.gradle.kts to define how benchmarks are executed and which platforms they run on.

    • configurations: Define profiles with specific settings like iterations, warmups, iterationTime, and iterationTimeUnit. The main profile is the default.
    • targets: Register the platforms (e.g., jvm, js) you want to benchmark.
    // build.gradle.kts
    benchmark {
        configurations {
            named("main") {
                iterations = 20
                warmups = 20
                iterationTime = 1
                iterationTimeUnit = "s"
            }
            register("smoke") {
                include("Essential") // Only run benchmarks matching this name/pattern
                iterations = 10
                warmups = 10
                iterationTime = 200
                iterationTimeUnit = "ms"
            }
        }
    
        targets {
            register("jvm")
            register("js")
        }
    }
  10. Set up a separate benchmark source set in Kotlin/JVM

    master

    For Kotlin/JVM projects, you can isolate benchmarks by creating a custom source set and associating its compilation with your main or test compilation. This enables benchmarks to access internal APIs and reuse test code.

    Step 1: Define the source set

    Create a new source set named benchmark.

    Step 2: Associate compilations

    Associate the benchmark compilation with the main compilation (or test if you want to reuse test code) to allow access to internal APIs and dependency propagation.

    Step 3: Register the benchmark target

    Register the source set with the benchmark extension so the tool knows where to find and execute benchmarks.

    Step 4: Add benchmark code

    Place your benchmark code into the newly created benchmark source set.

    // build.gradle.kts
    sourceSets {
        create("benchmark")
    }
    
    kotlin {
        target {
            compilations.getByName("benchmark")
                .associateWith(compilations.getByName("main"))
        }
    }
    
    benchmark {
        targets {
            register("benchmark")
        }
    }
  11. Set up a Kotlin/JVM or Java project for benchmarking

    master

    To use kotlinx-benchmark in a Kotlin/JVM or Java project, follow these configuration steps in your Gradle files:

    1. Apply the benchmark plugin in build.gradle.kts or build.gradle.
    2. Configure plugin management in settings.gradle.kts or settings.gradle to include gradlePluginPortal().
    3. Add the runtime dependency org.jetbrains.kotlinx:kotlinx-benchmark-runtime:<version> to your project dependencies.
    4. Add Maven Central to your repositories block for dependency lookup.
    5. Apply the allopen plugin (recommended for Kotlin) to ensure benchmark classes annotated with @State are open, allowing JMH to function correctly.
    6. Register your benchmark target in the benchmark block to tell the tool where your benchmarks are located (e.g., the main source set).
    // build.gradle.kts
    plugins {
        id("org.jetbrains.kotlinx.benchmark") version "0.4.13"
        kotlin("plugin.allopen") version "2.2.0"
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.4.13")
    }
    
    allOpen {
        annotation("org.openjdk.jmh.annotations.State")
    }
    
    benchmark {
        targets {
            register("main")
        }
    }
  12. Configure Kotlin/JVM benchmark target

    master

    To run benchmarks on the JVM, you must create a JVM target and register it in the benchmark block.

    Important: Because kotlinx-benchmark uses JMH under the hood, and Kotlin classes are final by default, you must apply the allopen plugin to ensure your benchmark classes (annotated with @State) and their methods are open. This allows JMH to generate the necessary subclasses.

    1. Create a jvm() target.
    2. Register jvm in the benchmark.targets block.
    3. Apply the allopen plugin and configure it to target @State annotations.
    // build.gradle.kts
    plugins {
        kotlin("jvm")
        kotlin("plugin.allopen") version "2.2.0"
    }
    
    kotlin {
        jvm()
    }
    
    benchmark {
        targets {
            register("jvm")
        }
    }
    
    allOpen {
        annotation("org.openjdk.jmh.annotations.State")
    }