├── integration ├── src │ ├── test │ │ ├── resources │ │ │ └── templates │ │ │ │ ├── transitive-dependencies-resolution │ │ │ │ ├── settings.gradle │ │ │ │ ├── gradle.properties │ │ │ │ ├── library │ │ │ │ │ ├── src │ │ │ │ │ │ └── commonMain │ │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── library.kt │ │ │ │ │ └── build.gradle │ │ │ │ ├── src │ │ │ │ │ └── commonMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ └── build.gradle │ │ │ │ ├── project-with-resources │ │ │ │ ├── src │ │ │ │ │ └── commonMain │ │ │ │ │ │ ├── resources │ │ │ │ │ │ └── dummy.file.txt │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ ├── gradle.properties │ │ │ │ └── build.gradle │ │ │ │ ├── es-modules │ │ │ │ ├── gradle.properties │ │ │ │ ├── build.gradle │ │ │ │ └── src │ │ │ │ │ └── commonMain │ │ │ │ │ └── kotlin │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ ├── config-options │ │ │ │ ├── gradle.properties │ │ │ │ ├── src │ │ │ │ │ └── commonMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ └── build.gradle │ │ │ │ ├── funny-names │ │ │ │ ├── gradle.properties │ │ │ │ ├── build.gradle │ │ │ │ └── src │ │ │ │ │ └── commonMain │ │ │ │ │ └── kotlin │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ ├── kotlin-native │ │ │ │ ├── gradle.properties │ │ │ │ ├── src │ │ │ │ │ ├── commonMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ │ └── nativeMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ └── DebugBenchmark.kt │ │ │ │ └── build.gradle │ │ │ │ ├── kotlin-multiplatform │ │ │ │ ├── gradle.properties │ │ │ │ ├── src │ │ │ │ │ ├── jsMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── RootBenchmark.kt │ │ │ │ │ ├── nativeMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── RootBenchmark.kt │ │ │ │ │ ├── wasmJsMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── RootBenchmark.kt │ │ │ │ │ └── commonMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ └── build.gradle │ │ │ │ ├── source-generation │ │ │ │ ├── gradle.properties │ │ │ │ ├── src │ │ │ │ │ └── commonMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ └── build.gradle │ │ │ │ ├── annotations-validation │ │ │ │ ├── gradle.properties │ │ │ │ ├── build.gradle │ │ │ │ └── src │ │ │ │ │ └── commonMain │ │ │ │ │ └── kotlin │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ ├── conflicting-jmh-versions │ │ │ │ ├── gradle.properties │ │ │ │ └── build.gradle │ │ │ │ ├── wasm-gc-non-experimental │ │ │ │ └── wasm-d8 │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src │ │ │ │ │ └── commonMain │ │ │ │ │ └── kotlin │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ ├── kmp-with-toolchain │ │ │ │ ├── min-supported-version │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src │ │ │ │ │ │ └── commonMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ └── CommonBenchmark.kt │ │ │ │ └── higher-version-than-in-gradle │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── settings.gradle │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src │ │ │ │ │ └── jvmMain │ │ │ │ │ └── kotlin │ │ │ │ │ └── JvmBenchmark.kt │ │ │ │ ├── kotlin-multiplatform-separate-source-set │ │ │ │ ├── gradle.properties │ │ │ │ ├── src │ │ │ │ │ ├── jsMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── JsTestData.kt │ │ │ │ │ ├── commonMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── CommonTestData.kt │ │ │ │ │ ├── jvmMain │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── JvmTestData.kt │ │ │ │ │ ├── jsCustom │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ │ └── JsCustomBenchmark.kt │ │ │ │ │ └── jvmCustom │ │ │ │ │ │ └── kotlin │ │ │ │ │ │ └── JvmCustomBenchmark.kt │ │ │ │ └── build.gradle │ │ │ │ └── invalid-target │ │ │ │ ├── js-d8 │ │ │ │ └── build.gradle │ │ │ │ ├── js-browser │ │ │ │ └── build.gradle │ │ │ │ ├── wasm-browser │ │ │ │ └── build.gradle │ │ │ │ └── wasm-nodejs │ │ │ │ ├── build.gradle │ │ │ │ └── src │ │ │ │ └── commonMain │ │ │ │ └── kotlin │ │ │ │ └── CommonBenchmark.kt │ │ └── kotlin │ │ │ └── kotlinx │ │ │ └── benchmark │ │ │ └── integration │ │ │ ├── KotlinNativeTest.kt │ │ │ ├── ProjectWithResourceFilesTest.kt │ │ │ ├── TransitiveDependenciesResolutionTest.kt │ │ │ ├── KotlinJsTest.kt │ │ │ ├── WasmGcOptionsTest.kt │ │ │ ├── JmhVersionValidationTest.kt │ │ │ ├── InvalidTargetingTest.kt │ │ │ ├── ReportFormatTest.kt │ │ │ ├── JvmToolchainsTest.kt │ │ │ ├── SourceSetAsBenchmarkTargetTest.kt │ │ │ ├── SupportedKotlinVersionTest.kt │ │ │ ├── GradleTest.kt │ │ │ ├── BenchmarkFunctionNameValidationTest.kt │ │ │ ├── testDsl.kt │ │ │ ├── SupportedGradleVersionTest.kt │ │ │ └── ConfigurationCacheTest.kt │ └── main │ │ └── kotlin │ │ └── kotlinx │ │ └── benchmark │ │ └── integration │ │ ├── GradleTestVersion.kt │ │ ├── BenchmarkTarget.kt │ │ ├── BenchmarkConfiguration.kt │ │ ├── Runner.kt │ │ ├── ProjectBuilder.kt │ │ └── AnnotationsSpecifier.kt └── build.gradle.kts ├── docs ├── notebooks-multi-run-demo.png ├── notebooks-single-run-demo.png ├── notebooks-compare-hypothesis-demo.png └── tasks-overview.md ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── examples ├── kotlin-jvm-separate-benchmark-source-set │ ├── main │ │ └── src │ │ │ └── TestData.kt │ ├── benchmarks │ │ └── src │ │ │ └── TestBenchmark.kt │ └── build.gradle.kts ├── build.gradle.kts ├── kotlin-multiplatform │ ├── src │ │ ├── jsMain │ │ │ └── kotlin │ │ │ │ ├── JsTestBenchmark.kt │ │ │ │ └── JsAsyncBenchmarks.kt │ │ ├── nativeMain │ │ │ └── kotlin │ │ │ │ └── NativeTestBenchmark.kt │ │ ├── wasmJsMain │ │ │ └── kotlin │ │ │ │ └── WasmTestBenchmark.kt │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ ├── nested │ │ │ │ └── CommonBenchmark.kt │ │ │ │ ├── ParamBenchmark.kt │ │ │ │ ├── InheritedBenchmark.kt │ │ │ │ └── CommonBenchmark.kt │ │ ├── jvmMain │ │ │ └── kotlin │ │ │ │ └── JvmTestBenchmark.kt │ │ └── jvmBenchmark │ │ │ └── kotlin │ │ │ └── JvmBenchmark.kt │ └── build.gradle.kts ├── java │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── test │ │ │ └── SampleJavaBenchmark.java │ └── build.gradle.kts ├── kotlin-jvm │ ├── main │ │ └── src │ │ │ └── JvmTestBenchmark.kt │ └── build.gradle.kts └── kotlin-jvm-compare-hypothesis │ ├── build.gradle.kts │ └── main │ └── src │ └── InverseSquareRootBenchmark.kt ├── .gitignore ├── runtime ├── api │ └── kotlinx-benchmark-runtime.api ├── commonMain │ └── src │ │ └── kotlinx │ │ └── benchmark │ │ ├── Utils.kt │ │ ├── Blackhole.kt │ │ ├── ReportBenchmarkResult.kt │ │ ├── BenchmarkDescriptor.kt │ │ ├── internal │ │ └── KotlinxBenchmarkPluginInternalApi.kt │ │ ├── SuiteDescriptor.kt │ │ ├── CommonBlackhole.kt │ │ ├── BenchmarkConfiguration.kt │ │ ├── CommonBenchmarkAnnotations.kt │ │ ├── RunnerConfiguration.kt │ │ ├── SuiteExecutor.kt │ │ └── IntelliJLog.kt ├── jvmMain │ └── src │ │ └── kotlinx │ │ └── benchmark │ │ ├── JvmBlackhole.kt │ │ ├── Utils.kt │ │ └── JvmBenchmarkAnnotations.kt ├── jsMain │ └── src │ │ └── kotlinx │ │ └── benchmark │ │ ├── D8EngineSupport.kt │ │ ├── UtilsJs.kt │ │ ├── NodeJsEngineSupport.kt │ │ ├── JsBenchmarkAnnotations.kt │ │ └── js │ │ ├── JsBuiltInExecutor.kt │ │ └── JsBenchmarkDescriptorWithAsync.kt ├── wasmJsMain │ └── src │ │ └── kotlinx │ │ └── benchmark │ │ ├── Utils.kt │ │ ├── WasmBenchmarkAnnotations.kt │ │ ├── wasm │ │ └── WasmBuiltInExecutor.kt │ │ ├── D8EngineSupport.kt │ │ └── NodeJsEngineSupport.kt ├── nativeMain │ └── src │ │ └── kotlinx │ │ └── benchmark │ │ ├── NativeIntelliJBenchmarkProgress.kt │ │ ├── NativeBenchmarkAnnotations.kt │ │ ├── Utils.kt │ │ └── NativeBlackhole.kt ├── commonTest │ └── src │ │ └── kotlinx │ │ └── benchmark │ │ └── tests │ │ └── FormatTests.kt └── jsWasmJsSharedMain │ └── src │ └── kotlinx │ └── benchmark │ └── CommonBlackhole.kt ├── plugin ├── gradle.properties ├── settings.gradle.kts └── main │ └── src │ └── kotlinx │ └── benchmark │ └── gradle │ ├── internal │ ├── generator │ │ ├── RequiresKotlinCompilerEmbeddable.kt │ │ └── workers │ │ │ ├── GenerateWasmSourceWorker.kt │ │ │ └── GenerateJsSourceWorker.kt │ ├── KotlinxBenchmarkPluginInternalApi.kt │ └── BenchmarkDependencies.kt │ ├── JvmMultiplatformTasks.kt │ ├── JmhBytecodeGeneratorTask.kt │ ├── WasmSourceGeneratorTask.kt │ ├── JvmJavaTasks.kt │ ├── JsSourceGeneratorTask.kt │ ├── ConsoleAndFilesOutputStream.kt │ ├── WasmMultiplatformTasks.kt │ ├── JsMultiplatformTasks.kt │ └── AnnotationsValidator.kt ├── gradle.properties ├── CODE_OF_CONDUCT.md ├── settings.gradle.kts ├── CONTRIBUTING.md ├── gradlew.bat └── .teamcity ├── pom.xml └── utils.kt /integration/src/test/resources/templates/transitive-dependencies-resolution/settings.gradle: -------------------------------------------------------------------------------- 1 | include("library") 2 | -------------------------------------------------------------------------------- /docs/notebooks-multi-run-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/kotlinx-benchmark/HEAD/docs/notebooks-multi-run-demo.png -------------------------------------------------------------------------------- /docs/notebooks-single-run-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/kotlinx-benchmark/HEAD/docs/notebooks-single-run-demo.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/kotlinx-benchmark/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/kotlin-jvm-separate-benchmark-source-set/main/src/TestData.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | data class TestData(var value: Double) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | .gradle/ 4 | build/ 5 | out/ 6 | target/ 7 | .DS_Store 8 | local.properties 9 | .kotlin/ 10 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/project-with-resources/src/commonMain/resources/dummy.file.txt: -------------------------------------------------------------------------------- 1 | I'm here to break your build! 2 | -------------------------------------------------------------------------------- /docs/notebooks-compare-hypothesis-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kotlin/kotlinx-benchmark/HEAD/docs/notebooks-compare-hypothesis-demo.png -------------------------------------------------------------------------------- /integration/src/test/resources/templates/es-modules/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/config-options/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/funny-names/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-native/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/source-generation/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/annotations-validation/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/conflicting-jmh-versions/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/project-with-resources/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/transitive-dependencies-resolution/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/wasm-gc-non-experimental/wasm-d8/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kmp-with-toolchain/min-supported-version/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform-separate-source-set/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform-separate-source-set/src/jsMain/kotlin/JsTestData.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | data class JsTestData(var value: CommonTestData) -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform-separate-source-set/src/commonMain/kotlin/CommonTestData.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | data class CommonTestData(var value: Double) -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform-separate-source-set/src/jvmMain/kotlin/JvmTestData.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | data class JvmTestData(var value: CommonTestData) -------------------------------------------------------------------------------- /integration/src/test/resources/templates/funny-names/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | jvm { } 3 | } 4 | 5 | benchmark { 6 | targets { 7 | register("jvm") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kmp-with-toolchain/higher-version-than-in-gradle/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /examples/build.gradle.kts: -------------------------------------------------------------------------------- 1 | group = "org.jetbrains.kotlinx.benchmark.examples" 2 | version = "0.1-SNAPSHOT" 3 | 4 | subprojects { 5 | repositories { 6 | mavenCentral() 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /runtime/api/kotlinx-benchmark-runtime.api: -------------------------------------------------------------------------------- 1 | public abstract interface annotation class kotlinx/benchmark/internal/KotlinxBenchmarkRuntimeInternalApi : java/lang/annotation/Annotation { 2 | } 3 | 4 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kmp-with-toolchain/higher-version-than-in-gradle/settings.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 3 | } 4 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/transitive-dependencies-resolution/library/src/commonMain/kotlin/library.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | public fun valuableWorkload(): Double { 4 | return 42.0 5 | } 6 | -------------------------------------------------------------------------------- /plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | group=org.jetbrains.kotlinx 2 | version=0.5.0-SNAPSHOT 3 | 4 | kotlin.code.style=official 5 | 6 | # to use fixated compiler for compiling Gradle plugin 7 | kotlin.compiler.runViaBuildToolsApi=true -------------------------------------------------------------------------------- /integration/src/test/resources/templates/invalid-target/js-d8/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | js { 3 | d8() 4 | } 5 | } 6 | 7 | benchmark { 8 | targets { 9 | register("js") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/invalid-target/js-browser/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | js { 3 | browser() 4 | } 5 | } 6 | 7 | benchmark { 8 | targets { 9 | register("js") 10 | } 11 | } -------------------------------------------------------------------------------- /integration/src/test/resources/templates/invalid-target/wasm-browser/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | wasmJs { 3 | browser() 4 | } 5 | } 6 | 7 | benchmark { 8 | targets { 9 | register("wasmJs") 10 | } 11 | } -------------------------------------------------------------------------------- /integration/src/test/resources/templates/invalid-target/wasm-nodejs/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | wasmJs { 3 | nodejs() 4 | } 5 | } 6 | 7 | benchmark { 8 | targets { 9 | register("wasmJs") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/Utils.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | internal expect fun String.readFile(): String 4 | 5 | internal expect fun String.writeFile(text: String) 6 | 7 | internal expect inline fun measureNanoseconds(block: () -> Unit): Long -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=org.jetbrains.kotlinx 2 | version=0.5.0-SNAPSHOT 3 | 4 | kotlin.code.style=official 5 | kotlin.incremental.multiplatform=true 6 | kotlin.native.distribution.type=prebuilt 7 | 8 | org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | This project and the corresponding community is governed by the [JetBrains Open Source and Community Code of Conduct](https://confluence.jetbrains.com/display/ALL/JetBrains+Open+Source+and+Community+Code+of+Conduct). Please make sure you read it. 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /runtime/jvmMain/src/kotlinx/benchmark/JvmBlackhole.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | actual typealias Blackhole = org.openjdk.jmh.infra.Blackhole 6 | 7 | @KotlinxBenchmarkRuntimeInternalApi 8 | actual fun Blackhole.flush() = Unit 9 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kmp-with-toolchain/min-supported-version/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | jvm {} 3 | } 4 | 5 | benchmark { 6 | targets { 7 | register("jvm") 8 | } 9 | 10 | configurations { 11 | main { 12 | advanced("jmhIgnoreLock", true) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kmp-with-toolchain/higher-version-than-in-gradle/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | jvm {} 3 | } 4 | 5 | benchmark { 6 | targets { 7 | register("jvm") 8 | } 9 | 10 | configurations { 11 | main { 12 | advanced("jmhIgnoreLock", true) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /plugin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | versionCatalogs { 9 | create("libs") { 10 | from(files("../gradle/libs.versions.toml")) 11 | } 12 | } 13 | } 14 | 15 | rootProject.name = "kotlinx-benchmark-plugin" 16 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/wasm-gc-non-experimental/wasm-d8/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.targets.js.d8.D8RootExtension 2 | 3 | kotlin { 4 | wasmJs { 5 | d8() 6 | } 7 | } 8 | 9 | benchmark { 10 | targets { 11 | register("wasmJs") 12 | } 13 | } 14 | 15 | rootProject.extensions.getByType(D8RootExtension).version = "12.6.99" 16 | -------------------------------------------------------------------------------- /integration/src/main/kotlin/kotlinx/benchmark/integration/GradleTestVersion.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | enum class GradleTestVersion(val versionString: String) { 4 | v8_0("8.0.2"), 5 | MinSupportedGradleVersion(System.getProperty("minSupportedGradleVersion")), 6 | UnsupportedGradleVersion("7.3"), 7 | MinSupportedKotlinVersion("2.0.0"), 8 | UnsupportedKotlinVersion("1.9.20"), 9 | } 10 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/es-modules/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | js("jsEs") { 3 | nodejs() 4 | useEsModules() 5 | } 6 | js("jsUmd") { 7 | nodejs() 8 | } 9 | js("jsCommon") { 10 | nodejs() 11 | useCommonJs() 12 | } 13 | } 14 | 15 | benchmark { 16 | targets { 17 | register("jsEs") 18 | register("jsUmd") 19 | register("jsCommon") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform/src/jsMain/kotlin/RootBenchmark.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.benchmark.* 2 | import kotlin.math.* 3 | 4 | @State(Scope.Benchmark) 5 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 6 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 7 | @BenchmarkMode(Mode.Throughput) 8 | open class RootBenchmark { 9 | @Benchmark 10 | open fun mathBenchmark(): Double { 11 | return log(sqrt(3.0) * cos(3.0), 2.0) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform/src/nativeMain/kotlin/RootBenchmark.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.benchmark.* 2 | import kotlin.math.* 3 | 4 | @State(Scope.Benchmark) 5 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 6 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 7 | @BenchmarkMode(Mode.Throughput) 8 | open class RootBenchmark { 9 | @Benchmark 10 | open fun mathBenchmark(): Double { 11 | return log(sqrt(3.0) * cos(3.0), 2.0) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform/src/wasmJsMain/kotlin/RootBenchmark.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.benchmark.* 2 | import kotlin.math.* 3 | 4 | @State(Scope.Benchmark) 5 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 6 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 7 | @BenchmarkMode(Mode.Throughput) 8 | open class RootBenchmark { 9 | @Benchmark 10 | open fun mathBenchmark(): Double { 11 | return log(sqrt(3.0) * cos(3.0), 2.0) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/jsMain/kotlin/JsTestBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | class JsTestBenchmark { 8 | private var data = 0.0 9 | 10 | @Setup 11 | fun setUp() { 12 | data = 3.0 13 | } 14 | 15 | @Benchmark 16 | fun sqrtBenchmark(): Double { 17 | return sqrt(data) 18 | } 19 | 20 | @Benchmark 21 | fun cosBenchmark(): Double { 22 | return cos(data) 23 | } 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 9 | @BenchmarkMode(Mode.Throughput) 10 | open class CommonBenchmark { 11 | @Benchmark 12 | open fun mathBenchmark(): Double { 13 | return log(sqrt(3.0) * cos(3.0), 2.0) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/nativeMain/kotlin/NativeTestBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | class NativeTestBenchmark { 8 | private var data = 0.0 9 | 10 | @Setup 11 | fun setUp() { 12 | data = 3.0 13 | } 14 | 15 | @Benchmark 16 | fun sqrtBenchmark(): Double { 17 | return sqrt(data) 18 | } 19 | 20 | @Benchmark 21 | fun cosBenchmark(): Double { 22 | return cos(data) 23 | } 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/wasmJsMain/kotlin/WasmTestBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | class WasmTestBenchmark { 8 | private var data = 0.0 9 | 10 | @Setup 11 | fun setUp() { 12 | data = 3.0 13 | } 14 | 15 | @Benchmark 16 | fun sqrtBenchmark(): Double { 17 | return sqrt(data) 18 | } 19 | 20 | @Benchmark 21 | fun cosBenchmark(): Double { 22 | return cos(data) 23 | } 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/Blackhole.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | expect class Blackhole { 6 | fun consume(obj: Any?) 7 | fun consume(bool: Boolean) 8 | fun consume(c: Char) 9 | fun consume(b: Byte) 10 | fun consume(s: Short) 11 | fun consume(i: Int) 12 | fun consume(l: Long) 13 | fun consume(f: Float) 14 | fun consume(d: Double) 15 | } 16 | 17 | @KotlinxBenchmarkRuntimeInternalApi 18 | expect fun Blackhole.flush() -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/ReportBenchmarkResult.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | @KotlinxBenchmarkRuntimeInternalApi 6 | class ReportBenchmarkResult( 7 | val config: BenchmarkConfiguration, 8 | val benchmark: BenchmarkDescriptor<*>, 9 | val params: Map, 10 | val score: Double, 11 | val error: Double, 12 | val confidence: Pair, 13 | val percentiles: Map, 14 | val values: DoubleArray 15 | ) 16 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/conflicting-jmh-versions/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | jvm { 3 | compilations.create('first') { associateWith(compilations.main) } 4 | compilations.create('second') { associateWith(compilations.main) } 5 | compilations.create('third') { associateWith(compilations.main) } 6 | } 7 | } 8 | 9 | benchmark { 10 | targets { 11 | register("jvmFirst") { jmhVersion = "1.21" } 12 | register("jvmSecond") { jmhVersion = "1.22" } 13 | register("jvmThird") { jmhVersion = "1.22" } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform-separate-source-set/build.gradle: -------------------------------------------------------------------------------- 1 | kotlin { 2 | jvm { 3 | compilations.create('custom') { associateWith(compilations.main) } 4 | } 5 | js { 6 | nodejs() 7 | compilations.create('custom') { associateWith(compilations.main) } 8 | } 9 | } 10 | 11 | benchmark { 12 | targets { 13 | register("jvmCustom") 14 | register("jsCustom") 15 | } 16 | 17 | configurations { 18 | main { 19 | advanced("jmhIgnoreLock", true) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/transitive-dependencies-resolution/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | 5 | @State(Scope.Benchmark) 6 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 7 | @Warmup(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 9 | @BenchmarkMode(Mode.Throughput) 10 | open class CommonBenchmark { 11 | @Benchmark 12 | open fun mathBenchmark(): Double { 13 | return valuableWorkload() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/es-modules/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @Warmup(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class CommonBenchmark { 12 | @Benchmark 13 | open fun benchmark(): Double { 14 | return log(sqrt(3.0) * cos(3.0), 2.0) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-native/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @Warmup(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class CommonBenchmark { 12 | @Benchmark 13 | open fun mathBenchmark(): Double { 14 | return log(sqrt(3.0) * cos(3.0), 2.0) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kmp-with-toolchain/min-supported-version/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | // Don't really need to measure anything here, just check that the benchmark works 7 | @State(Scope.Benchmark) 8 | @Warmup(iterations = 0) 9 | @Measurement(iterations = 1, time = 100, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 10 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 11 | @BenchmarkMode(Mode.Throughput) 12 | open class CommonBenchmark { 13 | @Benchmark 14 | open fun mathBenchmark() = 3.14 15 | } 16 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/project-with-resources/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @Warmup(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class CommonBenchmark { 12 | @Benchmark 13 | open fun mathBenchmark(): Double { 14 | return log(sqrt(3.0) * cos(3.0), 2.0) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/invalid-target/wasm-nodejs/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @Warmup(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class CommonBenchmark { 12 | @Benchmark 13 | open fun mathBenchmark(): Double { 14 | return log(sqrt(3.0) * cos(3.0), 2.0) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/wasm-gc-non-experimental/wasm-d8/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @Warmup(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class CommonBenchmark { 12 | @Benchmark 13 | open fun mathBenchmark(): Double { 14 | return log(sqrt(3.0) * cos(3.0), 2.0) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/java/src/main/java/test/SampleJavaBenchmark.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.openjdk.jmh.annotations.*; 4 | 5 | @State(Scope.Benchmark) 6 | @Fork(1) 7 | public class SampleJavaBenchmark { 8 | @Param({"A", "B"}) 9 | String stringValue; 10 | 11 | @Param({"1", "2"}) 12 | int intValue; 13 | 14 | @Benchmark 15 | public String stringBuilder() { 16 | StringBuilder stringBuilder = new StringBuilder(); 17 | stringBuilder.append(10); 18 | stringBuilder.append(stringValue); 19 | stringBuilder.append(intValue); 20 | return stringBuilder.toString(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/kotlin-jvm/main/src/JvmTestBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import org.openjdk.jmh.annotations.* 4 | import java.util.concurrent.* 5 | 6 | @State(Scope.Benchmark) 7 | @Fork(1) 8 | @Warmup(iterations = 0) 9 | @Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) 10 | class JvmTestBenchmark { 11 | private var data = 0.0 12 | 13 | @Setup 14 | fun setUp() { 15 | data = 3.0 16 | } 17 | 18 | @Benchmark 19 | fun sqrtBenchmark(): Double { 20 | return Math.sqrt(data) 21 | } 22 | 23 | @Benchmark 24 | fun cosBenchmark(): Double { 25 | return Math.cos(data) 26 | } 27 | } -------------------------------------------------------------------------------- /integration/src/main/kotlin/kotlinx/benchmark/integration/BenchmarkTarget.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | enum class JsBenchmarksExecutor { 4 | BenchmarkJs, 5 | BuiltIn 6 | } 7 | 8 | class BenchmarkTarget { 9 | var jmhVersion: String? = null 10 | var jsBenchmarksExecutor: JsBenchmarksExecutor? = null 11 | 12 | fun lines(name: String): List = """ 13 | register("$name") { 14 | ${jmhVersion?.let { "jmhVersion = \"$it\"" } ?: ""} 15 | ${jsBenchmarksExecutor?.let { "jsBenchmarksExecutor = JsBenchmarksExecutor.${it.name}" } ?: ""} 16 | } 17 | """.trimIndent().split("\n") 18 | } -------------------------------------------------------------------------------- /integration/src/test/resources/templates/config-options/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @Warmup(iterations = 2, time = 500, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class CommonBenchmark { 12 | 13 | @Param("3.0") 14 | var data: Double = 0.0 15 | 16 | @Benchmark 17 | fun mathBenchmark(): Double { 18 | return log(sqrt(data) * cos(data), 2.0) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform-separate-source-set/src/jsCustom/kotlin/JsCustomBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Warmup(iterations = 1, time = 100, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 8 | @Measurement(iterations = 1, time = 200, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | open class JsCustomBenchmark { 11 | @Benchmark 12 | open fun hashCodeBenchmark(): Int { 13 | val value = log(sqrt(3.0) * cos(3.0), 2.0) 14 | return JsTestData(CommonTestData(value)).hashCode() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /runtime/jvmMain/src/kotlinx/benchmark/Utils.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import java.io.File 4 | import java.util.* 5 | 6 | internal actual fun Double.format(precision: Int, useGrouping: Boolean): String { 7 | return if (useGrouping) "%,.0${precision}f".format(Locale.ROOT, this) 8 | else "%.0${precision}f".format(Locale.ROOT, this) 9 | } 10 | 11 | internal actual fun String.readFile(): String { 12 | return File(this).readText() 13 | } 14 | 15 | internal actual fun String.writeFile(text: String) { 16 | File(this).writeText(text) 17 | } 18 | 19 | internal actual inline fun measureNanoseconds(block: () -> Unit): Long = TODO("Not implemented for this platform") 20 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform-separate-source-set/src/jvmCustom/kotlin/JvmCustomBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Warmup(iterations = 1, time = 100, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 8 | @Measurement(iterations = 1, time = 200, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | open class JvmCustomBenchmark { 11 | @Benchmark 12 | open fun hashCodeBenchmark(): Int { 13 | val value = log(sqrt(3.0) * cos(3.0), 2.0) 14 | return JvmTestData(CommonTestData(value)).hashCode() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/KotlinNativeTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.junit.Test 4 | 5 | class KotlinNativeTest : GradleTest() { 6 | @Test 7 | fun debugBenchmarkTest() { 8 | project("kotlin-native", true).let { runner -> 9 | val target = "native" 10 | val capitalizedTarget = target.replaceFirstChar { it.uppercaseChar() } 11 | 12 | runner.runAndSucceed(":${target}BenchmarkGenerate") 13 | runner.runAndSucceed(":compile${capitalizedTarget}BenchmarkKotlin${capitalizedTarget}") 14 | runner.runAndSucceed(":${capitalizedTarget}Benchmark") 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-native/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 2 | import org.jetbrains.kotlin.konan.target.KonanTarget 3 | import org.jetbrains.kotlin.konan.target.HostManager 4 | 5 | kotlin { 6 | if (HostManager.hostIsLinux) linuxX64('native') 7 | if (HostManager.hostIsMingw) mingwX64('native') 8 | if (HostManager.host == KonanTarget.MACOS_ARM64.INSTANCE) macosArm64('native') 9 | if (HostManager.host == KonanTarget.MACOS_X64.INSTANCE) macosX64('native') 10 | } 11 | 12 | benchmark { 13 | targets { 14 | register("native") { 15 | buildType = NativeBuildType.DEBUG 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/ProjectWithResourceFilesTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.junit.Test 4 | 5 | // Regression tests for #185 6 | class ProjectWithResourceFilesTest : GradleTest() { 7 | private fun verifyFor(target: String) { 8 | project("project-with-resources", true).let { runner -> 9 | runner.runAndSucceed("${target}Benchmark") 10 | } 11 | } 12 | 13 | @Test 14 | fun js() { 15 | verifyFor("js") 16 | } 17 | 18 | @Test 19 | fun native() { 20 | verifyFor("native") 21 | } 22 | 23 | @Test 24 | fun wasmJs() { 25 | verifyFor("wasmJs") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/source-generation/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @Warmup(iterations = 2, time = 500, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class CommonBenchmark { 12 | 13 | var data1: Int = 0 14 | 15 | var data2: String = "" 16 | 17 | fun function1() { 18 | // println("function1") 19 | } 20 | 21 | fun function2() { 22 | // println("function2") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/kotlin-jvm-compare-hypothesis/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import kotlinx.benchmark.gradle.* 2 | 3 | plugins { 4 | kotlin("jvm") 5 | id("org.jetbrains.kotlinx.benchmark") 6 | } 7 | 8 | sourceSets.configureEach { 9 | java.setSrcDirs(listOf("$name/src")) 10 | resources.setSrcDirs(listOf("$name/resources")) 11 | } 12 | dependencies { 13 | implementation(project(":kotlinx-benchmark-runtime")) 14 | } 15 | 16 | kotlin { 17 | jvmToolchain(8) 18 | } 19 | 20 | benchmark { 21 | configurations { 22 | named("main") { 23 | iterationTime = 5 24 | iterationTimeUnit = "sec" 25 | 26 | } 27 | } 28 | targets { 29 | register("main") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/kotlin-jvm-separate-benchmark-source-set/benchmarks/src/TestBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import org.openjdk.jmh.annotations.* 4 | import java.util.concurrent.* 5 | import kotlin.math.* 6 | 7 | @State(Scope.Benchmark) 8 | @Fork(1) 9 | @Warmup(iterations = 0) 10 | @Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) 11 | class TestBenchmark { 12 | private var data: TestData = TestData(0.0) 13 | 14 | @Setup 15 | fun setUp() { 16 | data = TestData(3.0) 17 | } 18 | 19 | @Benchmark 20 | fun sqrtBenchmark(): Double { 21 | return sqrt(data.value) 22 | } 23 | 24 | @Benchmark 25 | fun cosBenchmark(): Double { 26 | return cos(data.value) 27 | } 28 | } -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/TransitiveDependenciesResolutionTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.junit.Test 4 | 5 | // Regression tests for #185 6 | class TransitiveDependenciesResolutionTest : GradleTest() { 7 | private fun verifyFor(target: String) { 8 | project("transitive-dependencies-resolution", true).let { runner -> 9 | runner.runAndSucceed("${target}Benchmark") 10 | } 11 | } 12 | 13 | @Test 14 | fun js() { 15 | verifyFor("js") 16 | } 17 | 18 | @Test 19 | fun native() { 20 | verifyFor("native") 21 | } 22 | 23 | @Test 24 | fun wasmJs() { 25 | verifyFor("wasmJs") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-native/src/nativeMain/kotlin/DebugBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.native.Platform 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @Warmup(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class DebugBenchmark { 12 | @Benchmark 13 | @OptIn(kotlin.experimental.ExperimentalNativeApi::class) 14 | open fun debugBenchmark(): String { 15 | return if (Platform.isDebugBinary) "Debug Benchmark" 16 | else error("Not a debug binary") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/commonMain/kotlin/nested/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test.nested 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | /** 7 | * This benchmark is to test that benchmarks with the same name but located in different packages 8 | * are handled correctly by the benchmarking plugin. 9 | */ 10 | @State(Scope.Benchmark) 11 | @Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 12 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 13 | @BenchmarkMode(Mode.Throughput) 14 | class CommonBenchmark { 15 | private var data = 0.0 16 | 17 | @Setup 18 | fun setUp() { 19 | data = 3.0 20 | } 21 | 22 | @Benchmark 23 | fun mathBenchmark(): Double { 24 | return log(sqrt(data) * cos(data), 2.0) 25 | } 26 | } -------------------------------------------------------------------------------- /examples/java/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import kotlinx.benchmark.gradle.JvmBenchmarkTarget 2 | 3 | plugins { 4 | java 5 | id("org.jetbrains.kotlinx.benchmark") 6 | } 7 | 8 | dependencies { 9 | implementation(project(":kotlinx-benchmark-runtime")) 10 | } 11 | 12 | benchmark { 13 | configurations { 14 | named("main") { 15 | iterationTime = 300 16 | iterationTimeUnit = "ms" 17 | } 18 | create("singleParam") { 19 | iterationTime = 300 20 | iterationTimeUnit = "ms" 21 | param("stringValue", "C", "D") 22 | param("intValue", 1, 10) 23 | } 24 | } 25 | targets { 26 | register("main") { 27 | this as JvmBenchmarkTarget 28 | jmhVersion = "1.37" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kmp-with-toolchain/higher-version-than-in-gradle/src/jvmMain/kotlin/JvmBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | 5 | // Don't really need to measure anything here, just check that the benchmark works 6 | @State(Scope.Benchmark) 7 | @Warmup(iterations = 0) 8 | @Measurement(iterations = 1, time = 100, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class JvmBenchmark { 12 | private val arrayList = java.util.ArrayList() 13 | 14 | @Setup 15 | fun setup() { 16 | // Methods from JDK21 17 | arrayList.addFirst(0) 18 | arrayList.addLast(1) 19 | } 20 | 21 | @Benchmark 22 | open fun benchmark() = arrayList.size 23 | } 24 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/KotlinJsTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import kotlin.test.Test 4 | 5 | class KotlinJsTest : GradleTest() { 6 | @Test 7 | fun useEsModules() { 8 | project("es-modules", true).runAndSucceed("jsEsBenchmark") { 9 | assertOutputContains("CommonBenchmark.benchmark") 10 | } 11 | } 12 | 13 | @Test 14 | fun useUmdModules() { 15 | project("es-modules", true).runAndSucceed("jsUmdBenchmark") { 16 | assertOutputContains("CommonBenchmark.benchmark") 17 | } 18 | } 19 | 20 | @Test 21 | fun useCommonJs() { 22 | project("es-modules", true).runAndSucceed("jsCommonBenchmark") { 23 | assertOutputContains("CommonBenchmark.benchmark") 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/jvmMain/kotlin/JvmTestBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import org.openjdk.jmh.annotations.* 4 | import java.util.concurrent.* 5 | 6 | const val WARMUP_ITERATIONS = 20 7 | @State(Scope.Benchmark) 8 | @Fork(1) 9 | @BenchmarkMode(Mode.AverageTime) 10 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 11 | @Warmup(iterations = WARMUP_ITERATIONS, time = 1, timeUnit = TimeUnit.SECONDS) 12 | @Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) 13 | class JvmTestBenchmark { 14 | private var data = 0.0 15 | 16 | @Setup 17 | fun setUp() { 18 | data = 3.0 19 | } 20 | 21 | @Benchmark 22 | fun sqrtBenchmark(): Double { 23 | return Math.sqrt(data) 24 | } 25 | 26 | @Benchmark 27 | fun cosBenchmark(): Double { 28 | return Math.cos(data) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/jvmBenchmark/kotlin/JvmBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import org.openjdk.jmh.annotations.* 4 | import java.util.concurrent.* 5 | 6 | @State(Scope.Benchmark) 7 | @Fork(1) 8 | @BenchmarkMode(Mode.AverageTime) 9 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 10 | // jvmBenchmark can access declarations from jvmMain! 11 | @Warmup(iterations = WARMUP_ITERATIONS, time = 1, timeUnit = TimeUnit.SECONDS) 12 | @Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) 13 | class JvmBenchmark { 14 | private var data = 0.0 15 | 16 | @Setup 17 | fun setUp() { 18 | data = 3.0 19 | } 20 | 21 | @Benchmark 22 | fun sqrtBenchmark(): Double { 23 | return Math.sqrt(data) 24 | } 25 | 26 | @Benchmark 27 | fun cosBenchmark(): Double { 28 | return Math.cos(data) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/internal/generator/RequiresKotlinCompilerEmbeddable.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle.internal.generator 2 | 3 | import kotlin.RequiresOptIn.Level.ERROR 4 | 5 | /** 6 | * This annotation indicates that a dependency on `kotlin-compiler-embeddable` is required at runtime. 7 | * 8 | * If code has this annotation, it **must** only be used inside a Gradle Worker with an isolated classpath. 9 | * The worker must have `kotlin-compiler-embeddable` available on the classpath. 10 | * 11 | * @see kotlinx.benchmark.gradle.internal.generator.workers.GenerateJsSourceWorker 12 | * @see kotlinx.benchmark.gradle.internal.generator.workers.GenerateWasmSourceWorker 13 | * @see kotlinx.benchmark.gradle.NativeSourceGeneratorWorker 14 | */ 15 | @RequiresOptIn(level = ERROR) 16 | @MustBeDocumented 17 | internal annotation class RequiresKotlinCompilerEmbeddable 18 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/BenchmarkDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | @KotlinxBenchmarkRuntimeInternalApi 6 | abstract class BenchmarkDescriptor( 7 | val name: String, 8 | val suite: SuiteDescriptor, 9 | val blackhole: Blackhole, 10 | ) 11 | 12 | @KotlinxBenchmarkRuntimeInternalApi 13 | open class BenchmarkDescriptorWithNoBlackholeParameter( 14 | name: String, 15 | suite: SuiteDescriptor, 16 | blackhole: Blackhole, 17 | val function: T.() -> Any?, 18 | ) : BenchmarkDescriptor(name, suite, blackhole) 19 | 20 | @KotlinxBenchmarkRuntimeInternalApi 21 | open class BenchmarkDescriptorWithBlackholeParameter( 22 | name: String, 23 | suite: SuiteDescriptor, 24 | blackhole: Blackhole, 25 | val function: T.(Blackhole) -> Any?, 26 | ) : BenchmarkDescriptor(name, suite, blackhole) 27 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/project-with-resources/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 2 | import org.jetbrains.kotlin.konan.target.KonanTarget 3 | import org.jetbrains.kotlin.konan.target.HostManager 4 | 5 | kotlin { 6 | if (HostManager.hostIsLinux) linuxX64('native') 7 | if (HostManager.hostIsMingw) mingwX64('native') 8 | if (HostManager.host == KonanTarget.MACOS_ARM64.INSTANCE) macosArm64('native') 9 | if (HostManager.host == KonanTarget.MACOS_X64.INSTANCE) macosX64('native') 10 | js() 11 | wasmJs { 12 | nodejs() 13 | } 14 | js() { 15 | nodejs() 16 | } 17 | } 18 | 19 | benchmark { 20 | targets { 21 | register("native") { 22 | // don't optimize binaries to speedup test execution 23 | buildType = NativeBuildType.DEBUG 24 | } 25 | register("js") 26 | register("wasmJs") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/internal/KotlinxBenchmarkPluginInternalApi.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.internal 2 | 3 | /** 4 | * Marks declarations that are **internal** to the kotlinx-benchmark project. 5 | * 6 | * Classes or functions marked with this annotation have `public` visibility 7 | * for historical or technical reasons. They are not intended for use by 8 | * kotlinx-benchmark users. They have no behaviour guarantees, and may lack clear 9 | * semantics, documentation, and backward compatibility. 10 | * 11 | * Please remove references to internal API to supported stable references. 12 | * If you still require access to these APIs, please raise an issue. 13 | */ 14 | @RequiresOptIn( 15 | message = "Internal kotlinx-benchmark API. It may be changed in future releases without notice.", 16 | level = RequiresOptIn.Level.WARNING 17 | ) 18 | @MustBeDocumented 19 | annotation class KotlinxBenchmarkRuntimeInternalApi 20 | -------------------------------------------------------------------------------- /examples/kotlin-jvm/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | import kotlinx.benchmark.gradle.* 4 | import org.jetbrains.kotlin.allopen.gradle.* 5 | 6 | 7 | plugins { 8 | kotlin("jvm") 9 | kotlin("plugin.allopen") version "2.0.20" 10 | id("org.jetbrains.kotlinx.benchmark") 11 | } 12 | 13 | sourceSets.configureEach { 14 | java.setSrcDirs(listOf("$name/src")) 15 | resources.setSrcDirs(listOf("$name/resources")) 16 | } 17 | 18 | configure { 19 | annotation("org.openjdk.jmh.annotations.State") 20 | } 21 | 22 | dependencies { 23 | implementation(project(":kotlinx-benchmark-runtime")) 24 | } 25 | 26 | kotlin { 27 | jvmToolchain(8) 28 | } 29 | 30 | benchmark { 31 | configurations { 32 | named("main") { 33 | iterationTime = 5 34 | iterationTimeUnit = "sec" 35 | 36 | } 37 | } 38 | targets { 39 | register("main") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/internal/KotlinxBenchmarkPluginInternalApi.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle.internal 2 | 3 | import kotlin.RequiresOptIn.Level.WARNING 4 | 5 | /** 6 | * Marks declarations that are **internal** to the kotlinx-benchmark project. 7 | * 8 | * Classes or functions marked with this annotation have `public` visibility 9 | * for historical or technical reasons. They are not intended for use by 10 | * kotlinx-benchmark users. They have no behaviour guarantees, and may lack clear 11 | * semantics, documentation, and backward compatibility. 12 | * 13 | * Please remove references to internal API to supported stable references. 14 | * If you still require access to these APIs, please raise an issue. 15 | */ 16 | @RequiresOptIn( 17 | message = "Internal kotlinx-benchmark API. It may be changed in future releases without notice.", 18 | level = WARNING, 19 | ) 20 | @MustBeDocumented 21 | annotation class KotlinxBenchmarkPluginInternalApi 22 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/commonMain/kotlin/ParamBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 9 | @BenchmarkMode(Mode.Throughput) 10 | class ParamBenchmark { 11 | 12 | @Param("1", "2") 13 | var data = 0 14 | 15 | @Param("1", "2") 16 | var value = 0 17 | 18 | @Param("""a "string" with quotes""") 19 | var text: String = "" 20 | 21 | @Benchmark 22 | fun mathBenchmark(): Double { 23 | return log(sqrt(data.toDouble()) * data, 2.0) 24 | } 25 | 26 | @Benchmark 27 | fun otherBenchmark(): Int { 28 | return data + data 29 | } 30 | 31 | @Benchmark 32 | fun textContentCheck(): String { 33 | check(text.length == 22) 34 | check(text.count { it == '\"' } == 2) 35 | return text 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /runtime/jvmMain/src/kotlinx/benchmark/JvmBenchmarkAnnotations.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | actual typealias Scope = org.openjdk.jmh.annotations.Scope 4 | 5 | actual typealias State = org.openjdk.jmh.annotations.State 6 | 7 | actual typealias Setup = org.openjdk.jmh.annotations.Setup 8 | 9 | actual typealias TearDown = org.openjdk.jmh.annotations.TearDown 10 | 11 | actual typealias Benchmark = org.openjdk.jmh.annotations.Benchmark 12 | 13 | actual typealias BenchmarkMode = org.openjdk.jmh.annotations.BenchmarkMode 14 | 15 | actual typealias Mode = org.openjdk.jmh.annotations.Mode 16 | 17 | actual typealias OutputTimeUnit = org.openjdk.jmh.annotations.OutputTimeUnit 18 | 19 | actual typealias BenchmarkTimeUnit = java.util.concurrent.TimeUnit 20 | 21 | actual typealias Warmup = org.openjdk.jmh.annotations.Warmup 22 | 23 | @Suppress("ACTUAL_ANNOTATION_CONFLICTING_DEFAULT_ARGUMENT_VALUE") 24 | actual typealias Measurement = org.openjdk.jmh.annotations.Measurement 25 | 26 | actual typealias Param = org.openjdk.jmh.annotations.Param -------------------------------------------------------------------------------- /integration/src/test/resources/templates/config-options/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 2 | import org.jetbrains.kotlin.konan.target.KonanTarget 3 | import org.jetbrains.kotlin.konan.target.HostManager 4 | 5 | kotlin { 6 | jvm { } 7 | js { nodejs() } 8 | wasmJs { d8() } 9 | 10 | if (HostManager.hostIsLinux) linuxX64('native') 11 | if (HostManager.hostIsMingw) mingwX64('native') 12 | if (HostManager.host == KonanTarget.MACOS_X64.INSTANCE) macosX64('native') 13 | if (HostManager.host == KonanTarget.MACOS_ARM64.INSTANCE) macosArm64('native') 14 | } 15 | 16 | benchmark { 17 | targets { 18 | register("jvm") 19 | register("js") 20 | register("wasmJs") 21 | register("native") { 22 | // don't optimize binaries to speedup test execution 23 | buildType = NativeBuildType.DEBUG 24 | } 25 | } 26 | 27 | configurations { 28 | main { 29 | advanced("jmhIgnoreLock", true) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/source-generation/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 2 | import org.jetbrains.kotlin.konan.target.KonanTarget 3 | import org.jetbrains.kotlin.konan.target.HostManager 4 | 5 | kotlin { 6 | jvm { } 7 | js { nodejs() } 8 | wasmJs { d8() } 9 | 10 | if (HostManager.hostIsLinux) linuxX64('native') 11 | if (HostManager.hostIsMingw) mingwX64('native') 12 | if (HostManager.host == KonanTarget.MACOS_X64.INSTANCE) macosX64('native') 13 | if (HostManager.host == KonanTarget.MACOS_ARM64.INSTANCE) macosArm64('native') 14 | } 15 | 16 | benchmark { 17 | targets { 18 | register("jvm") 19 | register("js") 20 | register("wasmJs") 21 | register("native") { 22 | // don't optimize binaries to speedup test execution 23 | buildType = NativeBuildType.DEBUG 24 | } 25 | } 26 | 27 | configurations { 28 | main { 29 | advanced("jmhIgnoreLock", true) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/annotations-validation/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 2 | import org.jetbrains.kotlin.konan.target.KonanTarget 3 | import org.jetbrains.kotlin.konan.target.HostManager 4 | 5 | kotlin { 6 | jvm { } 7 | js { nodejs() } 8 | wasmJs { d8() } 9 | 10 | if (HostManager.hostIsLinux) linuxX64('native') 11 | if (HostManager.hostIsMingw) mingwX64('native') 12 | if (HostManager.host == KonanTarget.MACOS_X64.INSTANCE) macosX64('native') 13 | if (HostManager.host == KonanTarget.MACOS_ARM64.INSTANCE) macosArm64('native') 14 | } 15 | 16 | benchmark { 17 | targets { 18 | register("jvm") 19 | register("js") 20 | register("wasmJs") 21 | register("native") { 22 | // don't optimize binaries to speedup test execution 23 | buildType = NativeBuildType.DEBUG 24 | } 25 | } 26 | 27 | configurations { 28 | main { 29 | advanced("jmhIgnoreLock", true) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/kotlin-multiplatform/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 2 | import org.jetbrains.kotlin.konan.target.KonanTarget 3 | import org.jetbrains.kotlin.konan.target.HostManager 4 | 5 | kotlin { 6 | jvm {} 7 | js { nodejs() } 8 | wasmJs { d8() } 9 | 10 | if (HostManager.hostIsLinux) linuxX64('native') 11 | if (HostManager.hostIsMingw) mingwX64('native') 12 | if (HostManager.host == KonanTarget.MACOS_X64.INSTANCE) macosX64('native') 13 | if (HostManager.host == KonanTarget.MACOS_ARM64.INSTANCE) macosArm64('native') 14 | } 15 | 16 | benchmark { 17 | targets { 18 | register("jvm") 19 | register("js") 20 | register("wasmJs") 21 | register("native") { 22 | // don't optimize binaries to speedup test execution 23 | buildType = NativeBuildType.DEBUG 24 | } 25 | } 26 | 27 | configurations { 28 | main { 29 | advanced("jmhIgnoreLock", true) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/WasmGcOptionsTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.junit.Test 4 | 5 | // TODO: Remove this test and the associated code that adds the "--experimental-wasm-gc" flag once 6 | // the minimum supported Kotlin version no longer supports D8 < 12.3.68. 7 | class WasmGcOptionsTest : GradleTest() { 8 | @Test 9 | fun d8() { 10 | // The default D8 version used with the current kotlin_version is 11.9.85 (or newer). 11 | // Therefore, the test project intentionally uses a version newer than 12.3.68. 12 | val runner = project( 13 | "wasm-gc-non-experimental/wasm-d8", 14 | print = true, 15 | // While Kotlin/Wasm is in active development phase, only matching versions are supported. 16 | // Hence, use the current Kotlin version instead of the minimum supported version. 17 | // kotlinVersion = GradleTestVersion.MinSupportedKotlinVersion.versionString 18 | ) 19 | runner.runAndSucceed( "wasmJsBenchmark") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/commonMain/kotlin/InheritedBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @OutputTimeUnit(BenchmarkTimeUnit.SECONDS) 8 | abstract class BaseBenchmark { 9 | protected var data = 0.0 10 | private lateinit var text: String 11 | 12 | @Setup 13 | fun baseSetup() { 14 | data = 3.0 15 | text = "Hello!" 16 | } 17 | 18 | @TearDown 19 | fun baseTeardown() { 20 | } 21 | 22 | @Benchmark 23 | fun baseBenchmark(): Double { 24 | var value = 1.0 25 | repeat(1000) { 26 | value *= text.length 27 | } 28 | return value 29 | } 30 | } 31 | 32 | @State(Scope.Benchmark) 33 | @Measurement(iterations = 10) 34 | class InheritedBenchmark : BaseBenchmark() { 35 | @Setup 36 | fun inheritedSetup() { 37 | } 38 | 39 | @TearDown 40 | fun inheritedTeardown() { 41 | } 42 | 43 | @Benchmark 44 | fun inheritedBenchmark(): Double { 45 | return log(sqrt(data) * cos(data), 2.0) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/JmhVersionValidationTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import kotlin.test.Test 4 | 5 | class JmhVersionValidationTest : GradleTest() { 6 | @Test 7 | fun verifyWarningsAboutJmhVersions() { 8 | val runner = project("conflicting-jmh-versions", true) {} 9 | 10 | runner.runAndSucceed("assembleBenchmarks") { 11 | assertOutputContains("configures several JVM benchmarking targets that use different " + 12 | "JMH versions (1.21 is used by jvmFirst; 1.22 is used by jvmSecond, jvmThird). " + 13 | "Such configuration is not supported and may lead to runtime errors. " + 14 | "Consider using the same JMH version across all benchmarking targets.") 15 | assertOutputContains("Configured JMH version (1.22) is older than a default version supplied by the benchmarking plugin") 16 | assertOutputContains("Configured JMH version (1.21) is older than a default version supplied by the benchmarking plugin") 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/transitive-dependencies-resolution/library/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.konan.target.HostManager 2 | import org.jetbrains.kotlin.konan.target.KonanTarget 3 | 4 | plugins { 5 | id 'org.jetbrains.kotlin.multiplatform' 6 | } 7 | 8 | kotlin { 9 | if (HostManager.hostIsLinux) linuxX64('native') 10 | if (HostManager.hostIsMingw) mingwX64('native') 11 | if (HostManager.host == KonanTarget.MACOS_ARM64.INSTANCE) macosArm64('native') 12 | if (HostManager.host == KonanTarget.MACOS_X64.INSTANCE) macosX64('native') 13 | wasmJs { 14 | nodejs() 15 | } 16 | js() { 17 | nodejs() 18 | } 19 | 20 | sourceSets { 21 | jsMain { 22 | dependencies { 23 | implementation(kotlin("test")) 24 | } 25 | } 26 | nativeMain { 27 | dependencies { 28 | implementation(kotlin("test")) 29 | } 30 | } 31 | wasmJsMain { 32 | dependencies { 33 | implementation(kotlin("test")) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /runtime/jsMain/src/kotlinx/benchmark/D8EngineSupport.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlin.time.Duration.Companion.milliseconds 4 | 5 | private external fun read(path: String): String 6 | private external fun write(text: String) 7 | 8 | internal object D8EngineSupport : JsEngineSupport() { 9 | override fun writeFile(path: String, text: String) { 10 | //WORKAROUND: D8 cannot write into files, this format will be parsed on gradle plugin side 11 | write("$text") 12 | } 13 | 14 | override fun readFile(path: String): String = 15 | read(path) 16 | 17 | override fun arguments(): Array { 18 | val arguments = js("globalThis.arguments.join(' ')") as String 19 | return arguments.split(' ').toTypedArray() 20 | } 21 | } 22 | 23 | internal inline fun d8MeasureTime(block: () -> Unit): Long { 24 | val performance = js("(typeof self !== 'undefined' ? self : globalThis).performance") 25 | val start = performance.now() 26 | block() 27 | val end = performance.now() 28 | val startInNs = (start as Double).milliseconds.inWholeNanoseconds 29 | val endInNs = (end as Double).milliseconds.inWholeNanoseconds 30 | return endInNs - startInNs 31 | } -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/InvalidTargetingTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import kotlin.test.* 4 | 5 | class InvalidTargetingTest : GradleTest() { 6 | 7 | @Test 8 | fun testWasmNodeJs() { 9 | val runner = project("invalid-target/wasm-nodejs", true) 10 | runner.runAndSucceed("wasmJsBenchmark") 11 | } 12 | 13 | @Test 14 | fun testWasmBrowser() { 15 | val runner = project("invalid-target/wasm-browser", true) 16 | runner.runAndFail("wasmJsBenchmark") { 17 | assertOutputContains("kotlinx-benchmark only supports d8() and nodejs() environments for Kotlin/Wasm.") 18 | } 19 | } 20 | 21 | @Test 22 | fun testJsD8() { 23 | val runner = project("invalid-target/js-d8", true) 24 | runner.runAndFail("jsBenchmark") { 25 | assertOutputContains("kotlinx-benchmark only supports nodejs() environment for Kotlin/JS.") 26 | } 27 | } 28 | 29 | @Test 30 | fun testJsBrowser() { 31 | val runner = project("invalid-target/js-browser", true) 32 | runner.runAndFail("jsBenchmark") { 33 | assertOutputContains("kotlinx-benchmark only supports nodejs() environment for Kotlin/JS.") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /runtime/jsMain/src/kotlinx/benchmark/UtilsJs.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | internal actual fun Double.format(precision: Int, useGrouping: Boolean): String { 4 | val options = js("{maximumFractionDigits:2, minimumFractionDigits:2, useGrouping:true}") 5 | options.minimumFractionDigits = precision 6 | options.maximumFractionDigits = precision 7 | options.useGrouping = useGrouping 8 | return this.asDynamic().toLocaleString("en-GB", options) as String 9 | } 10 | 11 | internal actual fun String.writeFile(text: String): Unit = jsEngineSupport.writeFile(this, text) 12 | 13 | internal actual fun String.readFile(): String = jsEngineSupport.readFile(this) 14 | 15 | internal abstract class JsEngineSupport { 16 | abstract fun writeFile(path: String, text: String) 17 | 18 | abstract fun readFile(path: String): String 19 | 20 | abstract fun arguments(): Array 21 | } 22 | 23 | internal val isD8: Boolean by lazy { 24 | js("typeof d8 !== 'undefined'") as Boolean 25 | } 26 | 27 | internal val jsEngineSupport: JsEngineSupport by lazy { 28 | if (isD8) D8EngineSupport else NodeJsEngineSupport 29 | } 30 | 31 | internal actual inline fun measureNanoseconds(block: () -> Unit): Long = 32 | if (isD8) d8MeasureTime(block) else nodeJsMeasureTime(block) 33 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/ReportFormatTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import java.io.* 4 | import kotlin.test.* 5 | 6 | class ReportFormatTest : GradleTest() { 7 | 8 | @Test 9 | fun testReportFormatFileNames() { 10 | val formats = listOf(null, "json", "csv", "scsv", "text") 11 | val targets = listOf("js", "wasmJs", "jvm", "native") 12 | 13 | val runner = project("kotlin-multiplatform", true) { 14 | formats.forEach { format -> 15 | configuration(format ?: "jsonDefault") { 16 | iterations = 1 17 | iterationTime = 100 18 | iterationTimeUnit = "ms" 19 | reportFormat = format 20 | advanced("jmhIgnoreLock", true) 21 | } 22 | } 23 | } 24 | 25 | formats.forEach { format -> 26 | val name = format ?: "jsonDefault" 27 | val ext = format ?: "json" 28 | runner.runAndSucceed("${name}Benchmark") 29 | val reports = reports(name) 30 | assertEquals(targets.size, reports.size) 31 | assertEquals(targets.map { "$it.$ext" }.toSet(), reports.map(File::getName).toSet()) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/transitive-dependencies-resolution/build.gradle: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 2 | import org.jetbrains.kotlin.konan.target.HostManager 3 | import org.jetbrains.kotlin.konan.target.KonanTarget 4 | 5 | def parentRepositories = repositories 6 | 7 | subprojects { 8 | repositories { 9 | parentRepositories.forEach { 10 | add(it) 11 | } 12 | } 13 | } 14 | 15 | kotlin { 16 | if (HostManager.hostIsLinux) linuxX64('native') 17 | if (HostManager.hostIsMingw) mingwX64('native') 18 | if (HostManager.host == KonanTarget.MACOS_ARM64.INSTANCE) macosArm64('native') 19 | if (HostManager.host == KonanTarget.MACOS_X64.INSTANCE) macosX64('native') 20 | wasmJs { 21 | nodejs() 22 | } 23 | js() { 24 | nodejs() 25 | } 26 | 27 | sourceSets { 28 | commonMain { 29 | dependencies { 30 | implementation(project(":library")) 31 | } 32 | } 33 | } 34 | } 35 | 36 | benchmark { 37 | targets { 38 | register("native") { 39 | // don't optimize binaries to speedup test execution 40 | buildType = NativeBuildType.DEBUG 41 | } 42 | register("js") 43 | register("wasmJs") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/JvmToolchainsTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.gradle.testkit.runner.TaskOutcome 4 | import org.junit.Test 5 | import kotlin.test.assertEquals 6 | 7 | class JvmToolchainsTest : GradleTest() { 8 | @Test 9 | fun testHigherVersionThanInGradle() { 10 | val runner = project( 11 | "kmp-with-toolchain/higher-version-than-in-gradle", 12 | print = true, 13 | GradleTestVersion.v8_0, 14 | jvmToolchain = 21 15 | ) 16 | runner.runAndSucceed("benchmark") { 17 | assertEquals(TaskOutcome.SUCCESS, task(":jvmBenchmark")!!.outcome) 18 | assertOutputDoesNotContain("") 19 | } 20 | } 21 | 22 | @Test 23 | fun testMinSupportedVersion() { 24 | for (jvmToolchain in listOf(8, 11, 17, 21)) { 25 | val runner = project( 26 | "kmp-with-toolchain/min-supported-version", 27 | print = true, 28 | jvmToolchain = jvmToolchain 29 | ) 30 | runner.runAndSucceed("benchmark") { 31 | assertEquals(TaskOutcome.SUCCESS, task(":jvmBenchmark")!!.outcome) 32 | assertOutputDoesNotContain("") 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/jsMain/kotlin/JsAsyncBenchmarks.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.js.* 5 | 6 | @State(Scope.Benchmark) 7 | class JsAsyncBenchmarks { 8 | 9 | private var data = 0.0 10 | 11 | @Setup 12 | fun setUp() { 13 | data = 42.0 14 | } 15 | 16 | @Benchmark 17 | fun baseline(): Double { 18 | // This benchmarks shows that benchmarks.js fails a baseline test :) 19 | return data 20 | } 21 | 22 | @Benchmark 23 | fun promiseResolve(): Promise { 24 | return Promise.resolve(data) 25 | } 26 | 27 | @Benchmark 28 | fun promiseResolveDoubleChain(): Promise { 29 | return Promise.resolve(Unit).then { data } 30 | } 31 | 32 | @Benchmark 33 | fun promiseResolveTripleChain(): Promise { 34 | return Promise.resolve(Unit).then { Unit }.then { data } 35 | } 36 | 37 | @Benchmark 38 | fun promiseDelayedBaseline(): Promise { 39 | // Score of this benchmark cannot be greater than 10 ops/sec 40 | return Promise { resolve, _ -> 41 | setTimeout({ 42 | resolve(data) 43 | }, 100) 44 | } 45 | } 46 | 47 | } 48 | 49 | private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int 50 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/funny-names/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.jvm.* 5 | 6 | @JvmInline 7 | value class StringWrapper(val value: String) 8 | 9 | @State(Scope.Benchmark) 10 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 11 | @Warmup(iterations = 1, time = 500, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 12 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 13 | @BenchmarkMode(Mode.Throughput) 14 | open class CommonBenchmark { 15 | @Benchmark // will be wrapString-something on JVM 16 | fun wrapString() = StringWrapper("Hello World!") 17 | 18 | @Benchmark 19 | @JvmName("-explicitlyRenamed") 20 | fun explicitlyRenamed() = 0 21 | 22 | @Benchmark 23 | fun `backticked name`() = 0 24 | 25 | @Benchmark 26 | fun `assert`() = 0 27 | } 28 | 29 | abstract class BenchmarkBase { 30 | @Benchmark 31 | @JvmName("-illegal base name") 32 | fun base() = 0 33 | } 34 | 35 | @State(Scope.Benchmark) 36 | @Measurement(iterations = 1, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 37 | @Warmup(iterations = 1, time = 500, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 38 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 39 | @BenchmarkMode(Mode.Throughput) 40 | open class ConcreteBenchmark : BenchmarkBase() 41 | -------------------------------------------------------------------------------- /runtime/jsMain/src/kotlinx/benchmark/NodeJsEngineSupport.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlin.time.DurationUnit 4 | import kotlin.time.toDuration 5 | 6 | @JsModule("fs") 7 | @JsNonModule 8 | private external object NodeFileSystem { 9 | fun writeFileSync(path: String, data: String) 10 | fun readFileSync(path: String, options: String): String 11 | } 12 | 13 | internal object NodeJsEngineSupport : JsEngineSupport() { 14 | override fun writeFile(path: String, text: String) = 15 | NodeFileSystem.writeFileSync(path, text) 16 | 17 | override fun readFile(path: String): String = 18 | NodeFileSystem.readFileSync(path, "utf8") 19 | 20 | override fun arguments(): Array { 21 | val arguments = js("process.argv.slice(2).join(' ')") as String 22 | return arguments.split(' ').toTypedArray() 23 | } 24 | } 25 | 26 | private fun hrTimeToNs(hrTime: dynamic): Long = (hrTime as Array).let { (seconds, nanos) -> 27 | seconds.toDuration(DurationUnit.SECONDS) + nanos.toDuration(DurationUnit.NANOSECONDS) }.inWholeNanoseconds 28 | 29 | internal inline fun nodeJsMeasureTime(block: () -> Unit): Long { 30 | val process = js("process") 31 | val start = process.hrtime() 32 | block() 33 | val end = process.hrtime() 34 | return hrTimeToNs(end) - hrTimeToNs(start) 35 | } 36 | -------------------------------------------------------------------------------- /runtime/wasmJsMain/src/kotlinx/benchmark/Utils.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | @JsFun("(d, precision, useGrouping) => d.toLocaleString('en-GB', { maximumFractionDigits: precision, minimumFractionDigits: precision, useGrouping: useGrouping } )") 4 | private external fun format(d: Double, precision: Int, useGrouping: Boolean): String 5 | 6 | internal actual fun Double.format(precision: Int, useGrouping: Boolean): String = format(this, precision, useGrouping) 7 | 8 | internal actual fun String.writeFile(text: String): Unit = jsEngineSupport.writeFile(this, text) 9 | 10 | internal actual fun String.readFile(): String = jsEngineSupport.readFile(this) 11 | 12 | internal abstract class JsEngineSupport { 13 | abstract fun writeFile(path: String, text: String) 14 | 15 | abstract fun readFile(path: String): String 16 | 17 | abstract fun arguments(): Array 18 | } 19 | 20 | @JsFun("() => typeof d8 !== 'undefined'") 21 | internal external fun isD8Impl(): Boolean 22 | 23 | internal val isD8: Boolean by lazy { 24 | isD8Impl() 25 | } 26 | 27 | internal val jsEngineSupport: JsEngineSupport by lazy { 28 | if (isD8) D8EngineSupport else NodeJsEngineSupport 29 | } 30 | 31 | internal external interface ExternalInterfaceType 32 | 33 | internal actual inline fun measureNanoseconds(block: () -> Unit): Long = 34 | if (isD8) d8MeasureTime(block) else nodeJsMeasureTime(block) 35 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/SourceSetAsBenchmarkTargetTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import java.io.* 4 | import kotlin.test.* 5 | 6 | class SourceSetAsBenchmarkTargetTest : GradleTest() { 7 | 8 | @Test 9 | fun testSupportForSourceSetsAsBenchmarkTargets() { 10 | val targets = listOf("jvmCustom", "jsCustom") 11 | 12 | val runner = 13 | project("kotlin-multiplatform-separate-source-set", true) { 14 | configuration("csv") { 15 | iterations = 1 16 | iterationTime = 100 17 | iterationTimeUnit = "ms" 18 | reportFormat = "csv" 19 | advanced("jmhIgnoreLock", true) 20 | } 21 | } 22 | 23 | runner.runAndSucceed("benchmark") { 24 | assertTasksExecuted(targets.map { ":${it}Benchmark" }) 25 | } 26 | val jsonReports = reports("main").map(File::getName).filter { it.endsWith(".json") } 27 | assertEquals(targets.map { "$it.json" }.toSet(), jsonReports.toSet()) 28 | 29 | runner.runAndSucceed("csvBenchmark") { 30 | assertTasksExecuted(targets.map { ":${it}CsvBenchmark" }) 31 | } 32 | val csvReports = reports("csv").map(File::getName).filter { it.endsWith(".csv") } 33 | assertEquals(targets.map { "$it.csv" }.toSet(), csvReports.toSet()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /runtime/jsMain/src/kotlinx/benchmark/JsBenchmarkAnnotations.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | actual enum class Scope { 4 | Benchmark 5 | } 6 | 7 | @Target(AnnotationTarget.CLASS) 8 | actual annotation class State(actual val value: Scope) 9 | 10 | @Target(AnnotationTarget.FUNCTION) 11 | actual annotation class Setup 12 | 13 | @Target(AnnotationTarget.FUNCTION) 14 | actual annotation class TearDown 15 | 16 | @Target(AnnotationTarget.FUNCTION) 17 | actual annotation class Benchmark 18 | 19 | 20 | @Target(AnnotationTarget.CLASS) 21 | actual annotation class BenchmarkMode(actual vararg val value: Mode) 22 | 23 | actual enum class Mode { 24 | Throughput, AverageTime 25 | } 26 | 27 | @Target(AnnotationTarget.CLASS) 28 | actual annotation class OutputTimeUnit(actual val value: BenchmarkTimeUnit) 29 | 30 | actual enum class BenchmarkTimeUnit { 31 | NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES 32 | } 33 | 34 | @Target(AnnotationTarget.CLASS) 35 | actual annotation class Warmup( 36 | actual val iterations: Int, 37 | actual val time: Int, 38 | actual val timeUnit: BenchmarkTimeUnit, 39 | actual val batchSize: Int 40 | ) 41 | 42 | @Target(AnnotationTarget.CLASS) 43 | actual annotation class Measurement( 44 | actual val iterations: Int, 45 | actual val time: Int, 46 | actual val timeUnit: BenchmarkTimeUnit, 47 | actual val batchSize: Int 48 | ) 49 | 50 | actual annotation class Param(actual vararg val value: String) -------------------------------------------------------------------------------- /runtime/wasmJsMain/src/kotlinx/benchmark/WasmBenchmarkAnnotations.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | actual enum class Scope { 4 | Benchmark 5 | } 6 | 7 | @Target(AnnotationTarget.CLASS) 8 | actual annotation class State(actual val value: Scope) 9 | 10 | @Target(AnnotationTarget.FUNCTION) 11 | actual annotation class Setup 12 | 13 | @Target(AnnotationTarget.FUNCTION) 14 | actual annotation class TearDown 15 | 16 | @Target(AnnotationTarget.FUNCTION) 17 | actual annotation class Benchmark 18 | 19 | 20 | @Target(AnnotationTarget.CLASS) 21 | actual annotation class BenchmarkMode(actual vararg val value: Mode) 22 | 23 | actual enum class Mode { 24 | Throughput, AverageTime 25 | } 26 | 27 | @Target(AnnotationTarget.CLASS) 28 | actual annotation class OutputTimeUnit(actual val value: BenchmarkTimeUnit) 29 | 30 | actual enum class BenchmarkTimeUnit { 31 | NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES 32 | } 33 | 34 | @Target(AnnotationTarget.CLASS) 35 | actual annotation class Warmup( 36 | actual val iterations: Int, 37 | actual val time: Int, 38 | actual val timeUnit: BenchmarkTimeUnit, 39 | actual val batchSize: Int 40 | ) 41 | 42 | @Target(AnnotationTarget.CLASS) 43 | actual annotation class Measurement( 44 | actual val iterations: Int, 45 | actual val time: Int, 46 | actual val timeUnit: BenchmarkTimeUnit, 47 | actual val batchSize: Int 48 | ) 49 | 50 | actual annotation class Param(actual vararg val value: String) -------------------------------------------------------------------------------- /examples/kotlin-jvm-separate-benchmark-source-set/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import kotlinx.benchmark.gradle.JvmBenchmarkTarget 2 | 3 | plugins { 4 | kotlin("jvm") 5 | id("org.jetbrains.kotlin.plugin.allopen") version "2.0.20" 6 | id("org.jetbrains.kotlinx.benchmark") 7 | } 8 | 9 | // how to apply plugin to a specific source set? 10 | allOpen { 11 | annotation("org.openjdk.jmh.annotations.State") 12 | } 13 | 14 | sourceSets { 15 | create("benchmarks") 16 | } 17 | 18 | sourceSets.configureEach { 19 | kotlin.setSrcDirs(listOf(file("$name/src"))) 20 | java.setSrcDirs(listOf(file("$name/src"))) 21 | resources.setSrcDirs(listOf(file("$name/resources"))) 22 | } 23 | 24 | kotlin { 25 | /* 26 | Associate the benchmarks with the main compilation. 27 | This will: 28 | 1. Allow 'benchmarks' to see all internals of 'main' 29 | 2. Forward all dependencies from 'main' to be also visible in 'benchmarks' 30 | */ 31 | target.compilations.getByName("benchmarks") 32 | .associateWith(target.compilations.getByName("main")) 33 | } 34 | 35 | // Add dependency to the benchmark runtime 36 | dependencies { 37 | "benchmarksImplementation"(project(":kotlinx-benchmark-runtime")) 38 | } 39 | 40 | 41 | // Configure benchmark 42 | benchmark { 43 | targets { 44 | register("benchmarks") { 45 | if (this is JvmBenchmarkTarget) { 46 | jmhVersion = "1.37" 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 8 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 9 | @BenchmarkMode(Mode.AverageTime) 10 | class CommonBenchmark { 11 | private var data = 0.0 12 | private lateinit var text : String 13 | 14 | @Setup 15 | fun setUp() { 16 | data = 3.0 17 | text = "Hello!" 18 | // println("Setup!") 19 | } 20 | 21 | @TearDown 22 | fun teardown() { 23 | // println("Teardown!") 24 | } 25 | 26 | @Benchmark 27 | fun exception() { 28 | try { 29 | fail() 30 | } catch (e: Throwable) { 31 | throw Exception("I failed!", e) 32 | } 33 | } 34 | 35 | private fun fail() { 36 | TODO("not implemented") 37 | } 38 | 39 | @Benchmark 40 | fun mathBenchmark(): Double { 41 | return log(sqrt(data) * cos(data), 2.0) 42 | } 43 | 44 | @Benchmark 45 | fun longBenchmark(): Double { 46 | var value = 1.0 47 | repeat(1000) { 48 | value *= text.length 49 | } 50 | return value 51 | } 52 | 53 | @Benchmark 54 | fun longBlackholeBenchmark(bh: Blackhole) { 55 | repeat(1000) { 56 | bh.consume(text.length) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/SupportedKotlinVersionTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class SupportedKotlinVersionTest : GradleTest() { 7 | 8 | /** The minimum Kotlin version that kotlinx-benchmark plugin supports. */ 9 | private val minSupportedKotlinVersion = System.getProperty("minSupportedKotlinVersion") 10 | private val warningMessage = 11 | "JetBrains Gradle Benchmarks plugin requires Kotlin version ${GradleTestVersion.MinSupportedKotlinVersion.versionString}" 12 | 13 | @Test 14 | fun `test MinSupportedKotlinVersion matches the version used in build scripts`() { 15 | assertEquals(minSupportedKotlinVersion, GradleTestVersion.MinSupportedKotlinVersion.versionString) 16 | } 17 | 18 | @Test 19 | fun `when using min supported Kotlin version, expect no warning`() { 20 | val runner = project("kotlin-multiplatform", kotlinVersion = GradleTestVersion.MinSupportedKotlinVersion.versionString) 21 | 22 | runner.runAndSucceed(":help", "-q") { 23 | assertOutputDoesNotContain(warningMessage) 24 | } 25 | } 26 | 27 | @Test 28 | fun `when using unsupported Gradle version, expect warning`() { 29 | val runner = project("kotlin-multiplatform", kotlinVersion = GradleTestVersion.UnsupportedKotlinVersion.versionString) 30 | 31 | runner.run(":help", "-q") { 32 | assertOutputContains(warningMessage) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /runtime/wasmJsMain/src/kotlinx/benchmark/wasm/WasmBuiltInExecutor.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.wasm 2 | 3 | import kotlinx.benchmark.* 4 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 5 | 6 | 7 | private external interface JsAny 8 | 9 | @JsFun("(p) => p") 10 | private external fun jsId(p: JsAny): JsAny 11 | 12 | private fun id(p: Any): Any { 13 | // TODO: Use dedicated type for passing Kotlin references to JS when it is available 14 | @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") 15 | return jsId(p as JsAny) 16 | } 17 | 18 | @KotlinxBenchmarkRuntimeInternalApi 19 | class WasmBuiltInExecutor( 20 | name: String, 21 | @Suppress("UNUSED_PARAMETER") dummy_args: Array 22 | ) : CommonSuiteExecutor(name, jsEngineSupport.arguments()[0]) { 23 | 24 | private val BenchmarkConfiguration.notUseJsBridge: Boolean 25 | get() = "false".equals(advanced["jsUseBridge"], ignoreCase = true) 26 | 27 | @Suppress("UNCHECKED_CAST") 28 | private fun createJsMeasurerBridge(originalMeasurer: () -> Long): () -> Long = 29 | id(originalMeasurer) as (() -> Long) 30 | 31 | override fun createIterationMeasurer( 32 | instance: T, 33 | benchmark: BenchmarkDescriptor, 34 | configuration: BenchmarkConfiguration, 35 | cycles: Int 36 | ): () -> Long { 37 | val measurer = super.createIterationMeasurer(instance, benchmark, configuration, cycles) 38 | return if (configuration.notUseJsBridge) measurer else createJsMeasurerBridge(measurer) 39 | } 40 | } -------------------------------------------------------------------------------- /runtime/nativeMain/src/kotlinx/benchmark/NativeIntelliJBenchmarkProgress.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | // This implementation is used in Kotlin/Native target, where each benchmark is run in a separate process. 6 | // Previous benchmark id is stored to identify when previous suite (benchmark class) ends. 7 | @KotlinxBenchmarkRuntimeInternalApi 8 | class NativeIntelliJBenchmarkProgress( 9 | private val benchProgressPath: String 10 | ) : IntelliJBenchmarkProgress() { 11 | 12 | init { 13 | decode() 14 | } 15 | 16 | override fun startBenchmark(suite: String, benchmark: String) { 17 | super.startBenchmark(suite, benchmark) 18 | encode() 19 | } 20 | 21 | override fun endBenchmarkException(suite: String, benchmark: String, error: String, stacktrace: String) { 22 | super.endBenchmarkException(suite, benchmark, error, stacktrace) 23 | encode() 24 | } 25 | 26 | private fun encode() { 27 | benchProgressPath.writeFile(listOf(currentClass, currentStatus, suiteStatus).joinToString(separator = "\n")) 28 | } 29 | 30 | private fun decode() { 31 | val content = benchProgressPath.readFile() 32 | if (content.isEmpty()) { 33 | return 34 | } 35 | 36 | val (currentClass, currentStatus, suiteStatus) = content.lines() 37 | this.currentClass = currentClass 38 | this.currentStatus = FinishStatus.valueOf(currentStatus) 39 | this.suiteStatus = FinishStatus.valueOf(suiteStatus) 40 | } 41 | } -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/JvmMultiplatformTasks.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi 4 | import org.gradle.api.* 5 | 6 | @KotlinxBenchmarkPluginInternalApi 7 | fun Project.processJvmCompilation(target: KotlinJvmBenchmarkTarget) { 8 | project.logger.info("Configuring benchmarks for '${target.name}' using Kotlin/JVM") 9 | val compilation = target.compilation 10 | 11 | configureMultiplatformJvmCompilation(target) 12 | 13 | val workerClasspath = this.createJmhGenerationRuntimeConfiguration(target.name, target.jmhVersion) 14 | createJvmBenchmarkGenerateSourceTask( 15 | target, 16 | workerClasspath, 17 | compilation.compileDependencyFiles, 18 | compilation.compileAllTaskName, 19 | compilation.output.allOutputs 20 | ) 21 | val runtimeClasspath = compilation.output.allOutputs + compilation.runtimeDependencyFiles 22 | createJvmBenchmarkCompileTask(target, runtimeClasspath) 23 | target.extension.configurations.forEach { 24 | createJvmBenchmarkExecTask(it, target, runtimeClasspath) 25 | } 26 | } 27 | 28 | private fun Project.configureMultiplatformJvmCompilation(target: KotlinJvmBenchmarkTarget) { 29 | checkJmhVersion(target) 30 | // Add JMH core library as an implementation dependency to the specified compilation 31 | val jmhCore = dependencies.create("${BenchmarksPlugin.JMH_CORE_DEPENDENCY}:${target.jmhVersion}") 32 | 33 | target.compilation.dependencies { 34 | implementation(jmhCore) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | val kotlinRepoUrl = providers.gradleProperty("kotlin_repo_url").orNull 5 | if (kotlinRepoUrl != null) { 6 | maven(kotlinRepoUrl) 7 | } 8 | } 9 | } 10 | 11 | dependencyResolutionManagement { 12 | versionCatalogs { 13 | create("libs") { 14 | listOf( 15 | "kotlin" to "kotlin_version", 16 | "minSupportedGradle" to "min_supported_gradle_version", 17 | ).forEach { (versionName, propertyName) -> 18 | val overrideVersion = providers.gradleProperty(propertyName).orNull 19 | if (!overrideVersion.isNullOrBlank()) { 20 | // Override the default version. 21 | // The only intended use-case is for testing dev Kotlin builds using kotlinx-benchmark. 22 | // These versions should not be overridden during regular development. 23 | version(versionName, overrideVersion) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | rootProject.name = "kotlinx-benchmark" 31 | 32 | includeBuild("plugin") 33 | 34 | include(":runtime") 35 | project(":runtime").name = "kotlinx-benchmark-runtime" 36 | 37 | include(":integration") 38 | 39 | include(":examples") 40 | include(":examples:kotlin-multiplatform") 41 | include(":examples:java") 42 | include(":examples:kotlin-jvm-separate-benchmark-source-set") 43 | include(":examples:kotlin-jvm") 44 | include(":examples:kotlin-jvm-compare-hypothesis") 45 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/SuiteDescriptor.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | @KotlinxBenchmarkRuntimeInternalApi 6 | object DefaultDescriptorParameters { 7 | val iterations = 5 8 | val warmups = 5 9 | val iterationTime = IterationTime(10, BenchmarkTimeUnit.SECONDS) 10 | val outputTimeUnit = BenchmarkTimeUnit.SECONDS 11 | val mode = Mode.Throughput 12 | } 13 | 14 | @KotlinxBenchmarkRuntimeInternalApi 15 | open class SuiteDescriptor( 16 | val name: String, 17 | val factory: () -> T, 18 | val parametrize: (T, Map) -> Unit, 19 | val setup: (T) -> Unit, 20 | val teardown: (T) -> Unit, 21 | 22 | val parameters: List, 23 | val defaultParameters: Map>, 24 | 25 | val iterations: Int = DefaultDescriptorParameters.iterations, 26 | val warmups: Int = DefaultDescriptorParameters.warmups, 27 | 28 | val iterationTime: IterationTime = DefaultDescriptorParameters.iterationTime, 29 | val outputTimeUnit: BenchmarkTimeUnit = DefaultDescriptorParameters.outputTimeUnit, 30 | val mode: Mode = DefaultDescriptorParameters.mode 31 | ) { 32 | private val _benchmarks = mutableListOf>() 33 | 34 | val benchmarks: List> get() = _benchmarks 35 | 36 | fun add(benchmark: BenchmarkDescriptor) { 37 | _benchmarks.add(benchmark) 38 | } 39 | } 40 | 41 | @KotlinxBenchmarkRuntimeInternalApi 42 | data class IterationTime(val value: Long, val timeUnit: BenchmarkTimeUnit) -------------------------------------------------------------------------------- /runtime/jsMain/src/kotlinx/benchmark/js/JsBuiltInExecutor.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.js 2 | 3 | import kotlinx.benchmark.* 4 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 5 | import kotlinx.benchmark.jsEngineSupport 6 | 7 | @KotlinxBenchmarkRuntimeInternalApi 8 | class JsBuiltInExecutor( 9 | name: String, 10 | @Suppress("UNUSED_PARAMETER") dummy_args: Array 11 | ) : CommonSuiteExecutor(name, jsEngineSupport.arguments()[0]) { 12 | 13 | private val BenchmarkConfiguration.notUseJsBridge: Boolean 14 | get() = "false".equals(advanced["jsUseBridge"], ignoreCase = true) 15 | 16 | override fun run( 17 | runnerConfiguration: RunnerConfiguration, 18 | benchmarks: List>, 19 | start: () -> Unit, 20 | complete: () -> Unit 21 | ) { 22 | if (benchmarks.any { it.isAsync }) { 23 | error("${JsBuiltInExecutor::class.simpleName} does not support async functions") 24 | } 25 | super.run(runnerConfiguration, benchmarks, start, complete) 26 | } 27 | 28 | private fun createJsMeasurerBridge(originalMeasurer: () -> Long): () -> Long = 29 | { originalMeasurer() } 30 | 31 | override fun createIterationMeasurer( 32 | instance: T, 33 | benchmark: BenchmarkDescriptor, 34 | configuration: BenchmarkConfiguration, 35 | cycles: Int 36 | ): () -> Long { 37 | val measurer = super.createIterationMeasurer(instance, benchmark, configuration, cycles) 38 | return if (configuration.notUseJsBridge) measurer else createJsMeasurerBridge(measurer) 39 | } 40 | } -------------------------------------------------------------------------------- /runtime/nativeMain/src/kotlinx/benchmark/NativeBenchmarkAnnotations.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | actual enum class Scope { 6 | Benchmark 7 | } 8 | 9 | @Target(AnnotationTarget.CLASS) 10 | actual annotation class State(actual val value: Scope) 11 | 12 | @Target(AnnotationTarget.FUNCTION) 13 | actual annotation class Setup 14 | 15 | @Target(AnnotationTarget.FUNCTION) 16 | actual annotation class TearDown 17 | 18 | @Target(AnnotationTarget.FUNCTION) 19 | actual annotation class Benchmark 20 | 21 | 22 | @Target(AnnotationTarget.CLASS) 23 | actual annotation class BenchmarkMode(actual vararg val value: Mode) 24 | 25 | actual enum class Mode { 26 | Throughput, AverageTime 27 | } 28 | 29 | @KotlinxBenchmarkRuntimeInternalApi 30 | enum class NativeFork { 31 | PerBenchmark, PerIteration 32 | } 33 | 34 | @Target(AnnotationTarget.CLASS) 35 | actual annotation class OutputTimeUnit(actual val value: BenchmarkTimeUnit) 36 | 37 | actual enum class BenchmarkTimeUnit { 38 | NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES 39 | } 40 | 41 | @Target(AnnotationTarget.CLASS) 42 | actual annotation class Warmup( 43 | actual val iterations: Int, 44 | actual val time: Int, 45 | actual val timeUnit: BenchmarkTimeUnit, 46 | actual val batchSize: Int 47 | ) 48 | 49 | @Target(AnnotationTarget.CLASS) 50 | actual annotation class Measurement( 51 | actual val iterations: Int, 52 | actual val time: Int, 53 | actual val timeUnit: BenchmarkTimeUnit, 54 | actual val batchSize: Int 55 | ) 56 | 57 | actual annotation class Param(actual vararg val value: String) -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/CommonBlackhole.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | private const val MAGIC_SIZE: Int = 13 6 | 7 | // TODO: Drop this class once it becomes internal 8 | @KotlinxBenchmarkRuntimeInternalApi 9 | open class CommonBlackhole { 10 | private val arrayOfAny: Array = arrayOfNulls(MAGIC_SIZE) 11 | private var currentAnyPosition: Int = 0 12 | private fun consumeAny(obj: Any?) { 13 | arrayOfAny[currentAnyPosition] = obj 14 | currentAnyPosition = if (currentAnyPosition == MAGIC_SIZE - 1) 0 else currentAnyPosition + 1 15 | } 16 | 17 | private val arrayOfInt: IntArray = IntArray(MAGIC_SIZE) 18 | private var currentIntPosition: Int = 0 19 | private fun consumeInt(i: Int) { 20 | arrayOfInt[currentIntPosition] = i 21 | currentIntPosition = if (currentIntPosition == MAGIC_SIZE - 1) 0 else currentIntPosition + 1 22 | } 23 | 24 | fun flushMe() { 25 | val sums = arrayOfAny.sumOf { it.hashCode() } + arrayOfInt.sum() 26 | println("Consumed blackhole value: $sums") 27 | } 28 | 29 | fun consume(obj: Any?) = consumeAny(obj) 30 | 31 | fun consume(bool: Boolean) = consumeInt(bool.hashCode()) 32 | 33 | fun consume(c: Char) = consumeInt(c.hashCode()) 34 | 35 | fun consume(b: Byte) = consumeInt(b.hashCode()) 36 | 37 | fun consume(s: Short) = consumeInt(s.hashCode()) 38 | 39 | fun consume(i: Int) = consumeInt(i.hashCode()) 40 | 41 | fun consume(l: Long) = consumeInt(l.hashCode()) 42 | 43 | fun consume(f: Float) = consumeInt(f.hashCode()) 44 | 45 | fun consume(d: Double) = consumeInt(d.hashCode()) 46 | } -------------------------------------------------------------------------------- /runtime/jsMain/src/kotlinx/benchmark/js/JsBenchmarkDescriptorWithAsync.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.js 2 | 3 | import kotlinx.benchmark.* 4 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 5 | import kotlin.js.* 6 | 7 | @KotlinxBenchmarkRuntimeInternalApi 8 | class JsBenchmarkDescriptorWithNoBlackholeParameter( 9 | name: String, 10 | suite: SuiteDescriptor, 11 | blackhole: Blackhole, 12 | function: T.() -> Any?, 13 | val async: Boolean = false 14 | ) : BenchmarkDescriptorWithNoBlackholeParameter(name, suite, blackhole, function) { 15 | constructor( 16 | name: String, 17 | suite: SuiteDescriptor, 18 | blackhole: Blackhole, 19 | function: T.() -> Promise<*> 20 | ) : this(name, suite, blackhole, function, true) 21 | } 22 | 23 | @KotlinxBenchmarkRuntimeInternalApi 24 | class JsBenchmarkDescriptorWithBlackholeParameter( 25 | name: String, 26 | suite: SuiteDescriptor, 27 | blackhole: Blackhole, 28 | function: T.(Blackhole) -> Any?, 29 | val async: Boolean = false 30 | ) : BenchmarkDescriptorWithBlackholeParameter(name, suite, blackhole, function) { 31 | constructor( 32 | name: String, 33 | suite: SuiteDescriptor, 34 | blackhole: Blackhole, 35 | function: T.(Blackhole) -> Promise<*> 36 | ) : this(name, suite, blackhole, function, true) 37 | } 38 | 39 | @OptIn(KotlinxBenchmarkRuntimeInternalApi::class) 40 | internal val BenchmarkDescriptor<*>.isAsync get() = when (this) { 41 | is JsBenchmarkDescriptorWithNoBlackholeParameter<*> -> async 42 | is JsBenchmarkDescriptorWithBlackholeParameter<*> -> async 43 | else -> error("Unexpected ${this::class.simpleName}") 44 | } -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/GradleTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.junit.* 4 | import org.junit.rules.* 5 | import java.io.* 6 | 7 | abstract class GradleTest { 8 | @Rule 9 | @JvmField 10 | internal val testProjectDir: TemporaryFolder = TemporaryFolder(File("build/temp").apply { mkdirs() }) 11 | private val rootProjectDir: File get() = testProjectDir.root 12 | 13 | fun file(path: String): File = rootProjectDir.resolve(path) 14 | 15 | fun reports(configuration: String): List { 16 | val folder = file("build/reports/benchmarks/$configuration") 17 | return folder.listFiles().orEmpty().flatMap { it?.listFiles().orEmpty().toList() } 18 | } 19 | 20 | fun project( 21 | name: String, 22 | print: Boolean = false, 23 | gradleVersion: GradleTestVersion? = null, 24 | kotlinVersion: String? = null, 25 | jvmToolchain: Int? = null, 26 | build: ProjectBuilder.() -> Unit = {} 27 | ): Runner { 28 | val builder = ProjectBuilder().apply { 29 | kotlinVersion?.let { this.kotlinVersion = it } 30 | jvmToolchain?.let { this.jvmToolchain = it } 31 | }.apply(build) 32 | rootProjectDir.deleteRecursively() 33 | templates.resolve(name).copyRecursively(rootProjectDir) 34 | file("build.gradle").modify(builder::build) 35 | val settingsFile = file("settings.gradle") 36 | if (!settingsFile.exists()) { 37 | file("settings.gradle").writeText("") // empty settings file 38 | } 39 | return Runner(rootProjectDir, print, gradleVersion) 40 | } 41 | } 42 | 43 | private val templates = File("src/test/resources/templates") 44 | 45 | private fun File.modify(fn: (String) -> String) { 46 | writeText(fn(readText())) 47 | } 48 | -------------------------------------------------------------------------------- /examples/kotlin-jvm-compare-hypothesis/main/src/InverseSquareRootBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import org.openjdk.jmh.annotations.Fork 5 | import kotlin.math.pow 6 | import kotlin.math.sqrt 7 | import kotlin.random.Random 8 | 9 | @State(Scope.Benchmark) 10 | @Fork(1) 11 | @Warmup(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 12 | @Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 13 | open class InverseSquareRootBenchmark { 14 | private var data = Random.nextFloat() 15 | 16 | @Benchmark 17 | fun invSqrtBaseline(): Float { 18 | return 1.0f / sqrt(data) 19 | } 20 | 21 | @Benchmark 22 | fun invSqrtOptimized(): Float { 23 | return fastInvSqrt(data) 24 | } 25 | } 26 | 27 | @State(Scope.Benchmark) 28 | @Fork(1) 29 | @Warmup(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 30 | @Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) 31 | open class PowerBenchmark { 32 | @Param("2.71") 33 | var power: Double = 0.0 34 | 35 | @Param("-3.0", "421431.243214") 36 | var value: Double = 0.0 37 | 38 | @Benchmark 39 | fun powerBaseline(): Double = value.pow(power) 40 | 41 | @Benchmark 42 | fun powerOptimized(): Double = fastPower(value, power) 43 | } 44 | 45 | // See https://en.wikipedia.org/wiki/Fast_inverse_square_root 46 | private fun fastInvSqrt(x: Float): Float { 47 | val y = Float.fromBits(0x5f3759df - (x.toBits() shr 1)) 48 | return y * (1.5F - (x * 0.5F * y * y)) 49 | } 50 | 51 | // Credits: https://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/ 52 | private fun fastPower(a: Double, b: Double): Double { 53 | val tmp = a.toBits() 54 | val tmp2 = (b * (tmp - 4606921280493453312L)).toLong() + 4606921280493453312L 55 | return Double.fromBits(tmp2) 56 | } 57 | -------------------------------------------------------------------------------- /runtime/commonTest/src/kotlinx/benchmark/tests/FormatTests.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.tests 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.test.* 5 | 6 | 7 | class FormatTests { 8 | @Test 9 | fun format() { 10 | assertEquals("1,200.0000", 1200.0.format(4)) 11 | assertEquals("12,000.0000", 12000.0.format(4)) 12 | assertEquals("1,200,000.0000", 1200000.0.format(4)) 13 | assertEquals("12.0300", 12.03.format(4)) 14 | assertEquals("1,200.0001", 1200.00005.format(4)) 15 | assertEquals("0.0000", 0.00000005.format(4)) 16 | assertEquals("0.0001", 0.00010005.format(4)) 17 | } 18 | 19 | @Test 20 | fun formatGrouping() { 21 | assertEquals("1200.0000", 1200.0.format(4, useGrouping = false)) 22 | assertEquals("12000.0000", 12000.0.format(4, useGrouping = false)) 23 | assertEquals("1200000.0000", 1200000.0.format(4, useGrouping = false)) 24 | assertEquals("12.0300", 12.03.format(4, useGrouping = false)) 25 | assertEquals("1200.0001", 1200.00005.format(4, useGrouping = false)) 26 | assertEquals("0.0000", 0.00000005.format(4, useGrouping = false)) 27 | assertEquals("0.0001", 0.00010005.format(4, useGrouping = false)) 28 | } 29 | 30 | @Test 31 | fun formatSignificant() { 32 | assertEquals("1,200", 1200.0.formatSignificant(4)) 33 | assertEquals("12.03", 12.03.formatSignificant(4)) 34 | assertEquals("1,200", 1200.00005.formatSignificant(4)) 35 | assertEquals("0.00000005000", 0.00000005.formatSignificant(4)) 36 | assertEquals("0.0001001", 0.00010005.formatSignificant(4)) 37 | } 38 | 39 | @Test 40 | fun nanosToText() { 41 | assertEquals("83.3333 ops/us", 12.0.nanosToText(Mode.Throughput, BenchmarkTimeUnit.MICROSECONDS)) 42 | assertEquals("0.0120000 us/op", 12.0.nanosToText(Mode.AverageTime, BenchmarkTimeUnit.MICROSECONDS)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/JmhBytecodeGeneratorTask.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi 4 | import org.gradle.api.* 5 | import org.gradle.api.file.* 6 | import org.gradle.api.provider.Provider 7 | import org.gradle.api.tasks.* 8 | import org.gradle.workers.* 9 | import java.io.* 10 | import javax.inject.* 11 | 12 | @CacheableTask 13 | open class JmhBytecodeGeneratorTask 14 | @KotlinxBenchmarkPluginInternalApi 15 | @Inject 16 | constructor( 17 | private val workerExecutor: WorkerExecutor 18 | ) : DefaultTask() { 19 | 20 | 21 | @Classpath 22 | lateinit var inputClassesDirs: FileCollection 23 | 24 | @Classpath 25 | lateinit var inputCompileClasspath: FileCollection 26 | 27 | @OutputDirectory 28 | lateinit var outputResourcesDir: File 29 | 30 | @OutputDirectory 31 | lateinit var outputSourcesDir: File 32 | 33 | @Classpath 34 | lateinit var runtimeClasspath: FileCollection 35 | 36 | @Optional 37 | @Input 38 | var executableProvider: Provider = project.provider { null } 39 | 40 | @TaskAction 41 | fun generate() { 42 | val workQueue = workerExecutor.processIsolation { workerSpec -> 43 | workerSpec.classpath.setFrom(runtimeClasspath.files) 44 | if (executableProvider.isPresent) { 45 | workerSpec.forkOptions.executable = executableProvider.get() 46 | } 47 | } 48 | workQueue.submit(JmhBytecodeGeneratorWorker::class.java) { workParameters -> 49 | workParameters.inputClasses.setFrom(inputClassesDirs.files) 50 | workParameters.inputClasspath.setFrom(inputCompileClasspath.files) 51 | workParameters.outputSourceDirectory.set(outputSourcesDir) 52 | workParameters.outputResourceDirectory.set(outputResourcesDir) 53 | } 54 | workQueue.await() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/BenchmarkFunctionNameValidationTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import kotlin.test.* 4 | 5 | class BenchmarkFunctionNameValidationTest : GradleTest() { 6 | @Test 7 | fun jvmNamesValidations() { 8 | project("funny-names").apply { 9 | runAndFail("jvmBenchmarkCompile") { 10 | assertOutputContains("One or more benchmark functions are invalid and could not be processed by JMH. See logs for details.") 11 | assertOutputDoesNotContain("Group name should be the legal Java identifier") 12 | 13 | assertOutputContains("""Benchmark function name is a reserved Java keyword and cannot be used: "test.CommonBenchmark.assert" (declared as "test.CommonBenchmark.assert")""") 14 | 15 | assertOutputContains("""Benchmark function name is not a valid Java identifier: "test.BenchmarkBase.-illegal base name" (declared as "test.BenchmarkBase.base")""") 16 | val firstOccurrence = output.indexOf(""""test.BenchmarkBase.-illegal base name"""") 17 | assertEquals(-1, output.indexOf(""""test.BenchmarkBase.-illegal base name"""", firstOccurrence + 1), 18 | "\"test.BenchmarkBase.-illegal base name\" is expected to be reported only once") 19 | 20 | assertOutputContains("""Benchmark function name is not a valid Java identifier: "test.CommonBenchmark.wrapString-gu_FwkY" (declared as "test.CommonBenchmark.wrapString")""") 21 | assertOutputContains("""Benchmark function name is not a valid Java identifier: "test.CommonBenchmark.-explicitlyRenamed" (declared as "test.CommonBenchmark.explicitlyRenamed")""") 22 | assertOutputContains("""Benchmark function name is not a valid Java identifier: "test.CommonBenchmark.backticked name" (declared as "test.CommonBenchmark.backticked name")""") 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /runtime/jsWasmJsSharedMain/src/kotlinx/benchmark/CommonBlackhole.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | private const val MAGIC_SIZE: Int = 13 6 | 7 | @Suppress("NOTHING_TO_INLINE") 8 | public actual class Blackhole { 9 | private val arrayOfAny: Array = arrayOfNulls(MAGIC_SIZE) 10 | private var currentAnyPosition: Int = 0 11 | 12 | @PublishedApi 13 | internal fun consumeAny(obj: Any?) { 14 | arrayOfAny[currentAnyPosition] = obj 15 | currentAnyPosition = if (currentAnyPosition == MAGIC_SIZE - 1) 0 else currentAnyPosition + 1 16 | } 17 | 18 | private val arrayOfInt: IntArray = IntArray(MAGIC_SIZE) 19 | private var currentIntPosition: Int = 0 20 | 21 | @PublishedApi 22 | internal fun consumeInt(i: Int) { 23 | arrayOfInt[currentIntPosition] = i 24 | currentIntPosition = if (currentIntPosition == MAGIC_SIZE - 1) 0 else currentIntPosition + 1 25 | } 26 | 27 | internal fun flushMe() { 28 | val sums = arrayOfAny.sumOf { it.hashCode() } + arrayOfInt.sum() 29 | println("Consumed blackhole value: $sums") 30 | } 31 | 32 | actual inline fun consume(obj: Any?) = consumeAny(obj) 33 | 34 | actual inline fun consume(bool: Boolean) = consumeInt(bool.hashCode()) 35 | 36 | actual inline fun consume(c: Char) = consumeInt(c.hashCode()) 37 | 38 | actual inline fun consume(b: Byte) = consumeInt(b.hashCode()) 39 | 40 | actual inline fun consume(s: Short) = consumeInt(s.hashCode()) 41 | 42 | actual inline fun consume(i: Int) = consumeInt(i.hashCode()) 43 | 44 | actual inline fun consume(l: Long) = consumeInt(l.hashCode()) 45 | 46 | actual inline fun consume(f: Float) = consumeInt(f.hashCode()) 47 | 48 | actual inline fun consume(d: Double) = consumeInt(d.hashCode()) 49 | } 50 | 51 | 52 | @KotlinxBenchmarkRuntimeInternalApi 53 | actual fun Blackhole.flush() = flushMe() 54 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/testDsl.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | import org.gradle.testkit.runner.TaskOutcome 5 | 6 | internal fun BuildResult.printBuildOutput() { 7 | println( 8 | """ 9 | |Failed assertion build output: 10 | |####################### 11 | |$output 12 | |####################### 13 | | 14 | """.trimMargin() 15 | ) 16 | } 17 | 18 | internal fun BuildResult.assertTasksExecuted(vararg tasks: String) { 19 | tasks.forEach { task -> 20 | assert(task(task)?.outcome == TaskOutcome.SUCCESS) { 21 | printBuildOutput() 22 | "Task $task didn't have 'SUCCESS' state: ${task(task)?.outcome}" 23 | } 24 | } 25 | } 26 | 27 | internal fun BuildResult.assertTasksExecuted(tasks: Collection) = assertTasksExecuted(*tasks.toTypedArray()) 28 | 29 | fun BuildResult.assertTasksUpToDate(vararg tasks: String) { 30 | tasks.forEach { task -> 31 | assert(task(task)?.outcome == TaskOutcome.UP_TO_DATE) { 32 | printBuildOutput() 33 | "Task $task didn't have 'UP-TO-DATE' state: ${task(task)?.outcome}" 34 | } 35 | } 36 | } 37 | 38 | internal fun BuildResult.assertOutputContains( 39 | expectedSubString: String, 40 | message: String = "Build output does not contain \"$expectedSubString\"" 41 | ) { 42 | assert(output.contains(expectedSubString)) { 43 | printBuildOutput() 44 | message 45 | } 46 | } 47 | 48 | internal fun BuildResult.assertOutputDoesNotContain( 49 | expectedSubString: String, 50 | message: String = "Build output contains \"$expectedSubString\"" 51 | ) { 52 | assert(!output.contains(expectedSubString)) { 53 | printBuildOutput() 54 | message 55 | } 56 | } 57 | 58 | internal fun BuildResult.assertTasksUpToDate(tasks: Collection) = assertTasksUpToDate(*tasks.toTypedArray()) 59 | -------------------------------------------------------------------------------- /integration/src/test/resources/templates/annotations-validation/src/commonMain/kotlin/CommonBenchmark.kt: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import kotlinx.benchmark.* 4 | import kotlin.math.* 5 | 6 | @State(Scope.Benchmark) 7 | @Measurement(iterations = 1, time = 1500, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 8 | @Warmup(iterations = 1, time = 500, timeUnit = BenchmarkTimeUnit.MILLISECONDS) 9 | @OutputTimeUnit(BenchmarkTimeUnit.MILLISECONDS) 10 | @BenchmarkMode(Mode.Throughput) 11 | open class CommonBenchmark { 12 | 13 | var varProperty: String = "var" 14 | 15 | val valProperty: String = "val" 16 | 17 | private var privateProperty: String = "private" 18 | 19 | internal var internalProperty: String = "internal" 20 | 21 | protected var protectedProperty: String = "protected" 22 | 23 | final var finalProperty: String = "final" 24 | 25 | var nullableStringProperty: String? = null 26 | 27 | var nullableIntProperty: Int? = null 28 | 29 | var nullableUIntProperty: UInt? = null 30 | 31 | var notSupportedTypeProperty: Regex = Regex("notSupportedType") 32 | 33 | fun plainFunction() { 34 | // println("Plain function!") 35 | } 36 | 37 | private fun privateFunction() { 38 | // println("Private function!") 39 | } 40 | 41 | internal fun internalFunction() { 42 | // println("Internal function!") 43 | } 44 | 45 | protected open fun protectedFunction() { 46 | // println("Protected function!") 47 | } 48 | 49 | final fun finalFunction() { 50 | // println("Final function!") 51 | } 52 | 53 | fun functionWithBlackholeArgument(bh: Blackhole) { 54 | val result = sqrt(3.0) 55 | bh.consume(result) 56 | } 57 | 58 | fun functionWithTwoBlackholeArguments(bh1: Blackhole, bh2: Blackhole) { 59 | val result = sqrt(3.0) 60 | bh1.consume(result) 61 | bh2.consume(result) 62 | } 63 | 64 | fun functionWithIntArgument(number: Int): Double { 65 | return sqrt(number.toDouble()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /integration/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | evaluationDependsOn(":kotlinx-benchmark-runtime") 10 | 11 | val runtime get() = project(":kotlinx-benchmark-runtime") 12 | val plugin get() = gradle.includedBuild("plugin") 13 | 14 | dependencies { 15 | implementation(gradleTestKit()) 16 | 17 | testImplementation(kotlin("test-junit")) 18 | } 19 | 20 | tasks.test { 21 | dependsOn(plugin.task(":publishToBuildLocal")) 22 | dependsOn(runtime.tasks.getByName("publishToBuildLocal")) 23 | 24 | systemProperty("plugin_repo_url", plugin.projectDir.resolve("build/maven").absoluteFile.invariantSeparatorsPath) 25 | systemProperty("runtime_repo_url", rootProject.buildDir.resolve("maven").absoluteFile.invariantSeparatorsPath) 26 | getKotlinDevRepositoryUrl(project)?.let { 27 | systemProperty("kotlin_repo_url", it) 28 | } 29 | systemProperty("kotlin_version", libs.versions.kotlin.asProvider().get()) 30 | getOverriddenKotlinLanguageVersion(project)?.let { 31 | systemProperty("kotlin_language_version", it) 32 | } 33 | getOverriddenKotlinApiVersion(project)?.let { 34 | systemProperty("kotlin_api_version", it) 35 | } 36 | getOverriddenKotlinNativeVersion(project)?.let { 37 | systemProperty("kotlin.native.version", it) 38 | } 39 | systemProperty("minSupportedGradleVersion", libs.versions.minSupportedGradle.get()) 40 | systemProperty("minSupportedKotlinVersion", libs.versions.minSupportedKotlin.get()) 41 | systemProperty("kotlin_Werror_override", if (getAllWarningsAsErrorsValue(project)) "enable" else "disable") 42 | project.providers.gradleProperty("kotlin_additional_cli_options").orNull?.let { 43 | systemProperty("kotlin_additional_cli_options", it) 44 | } 45 | 46 | val forks = project.providers.gradleProperty("testing.max.forks").orNull?.toInt() 47 | ?: (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) 48 | 49 | maxParallelForks = forks 50 | } 51 | -------------------------------------------------------------------------------- /integration/src/main/kotlin/kotlinx/benchmark/integration/BenchmarkConfiguration.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | class BenchmarkConfiguration { 4 | var iterations: Int? = null 5 | var warmups: Int? = null 6 | var iterationTime: Long? = null 7 | var iterationTimeUnit: String? = null 8 | var mode: String? = null 9 | var outputTimeUnit: String? = null 10 | var reportFormat: String? = null 11 | private var includes: MutableList = mutableListOf() 12 | private var excludes: MutableList = mutableListOf() 13 | private var params: MutableMap> = mutableMapOf() 14 | private var advanced: MutableMap = mutableMapOf() 15 | 16 | fun include(pattern: String) { 17 | includes.add(pattern) 18 | } 19 | 20 | fun exclude(pattern: String) { 21 | excludes.add(pattern) 22 | } 23 | 24 | fun param(name: String, vararg value: Any) { 25 | val values = params.getOrPut(name) { mutableListOf() } 26 | values.addAll(value) 27 | } 28 | 29 | fun advanced(name: String, value: Any) { 30 | advanced[name] = value 31 | } 32 | 33 | fun lines(name: String): List = """ 34 | $name { 35 | iterations = $iterations 36 | warmups = $warmups 37 | iterationTime = $iterationTime 38 | iterationTimeUnit = ${iterationTimeUnit?.escape()} 39 | mode = ${mode?.escape()} 40 | outputTimeUnit = ${outputTimeUnit?.escape()} 41 | reportFormat = ${reportFormat?.escape()} 42 | includes = ${includes.map { it.escape() }} 43 | excludes = ${excludes.map { it.escape() }} 44 | ${params.entries.joinToString(separator = "\n") { """param("${it.key}", ${it.value.joinToString()})""" }} 45 | ${advanced.entries.joinToString(separator = "\n") { 46 | """advanced("${it.key}", ${if (it.value is String) "\"${it.value}\"" else it.value})""" 47 | }} 48 | } 49 | """.trimIndent().split("\n") 50 | } 51 | 52 | private fun String.escape(): String = "\"$this\"" -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/WasmSourceGeneratorTask.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi 4 | import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable 5 | import kotlinx.benchmark.gradle.internal.generator.workers.GenerateWasmSourceWorker 6 | import org.gradle.api.* 7 | import org.gradle.api.file.* 8 | import org.gradle.api.tasks.* 9 | import org.gradle.workers.WorkerExecutor 10 | import java.io.File 11 | import javax.inject.Inject 12 | 13 | @CacheableTask 14 | abstract class WasmSourceGeneratorTask 15 | @KotlinxBenchmarkPluginInternalApi 16 | @Inject 17 | constructor( 18 | private val workerExecutor: WorkerExecutor 19 | ) : DefaultTask() { 20 | 21 | @Input 22 | lateinit var title: String 23 | 24 | @Classpath 25 | lateinit var inputClassesDirs: FileCollection 26 | 27 | @Classpath 28 | lateinit var inputDependencies: FileCollection 29 | 30 | @OutputDirectory 31 | lateinit var outputResourcesDir: File 32 | 33 | @OutputDirectory 34 | lateinit var outputSourcesDir: File 35 | 36 | @get:Classpath 37 | abstract val runtimeClasspath: ConfigurableFileCollection 38 | 39 | @TaskAction 40 | fun generate() { 41 | val workQueue = workerExecutor.classLoaderIsolation { 42 | it.classpath.from(runtimeClasspath) 43 | } 44 | 45 | @OptIn(RequiresKotlinCompilerEmbeddable::class) 46 | workQueue.submit(GenerateWasmSourceWorker::class.java) { 47 | it.title.set(title) 48 | it.inputClasses.from(inputClassesDirs) 49 | it.inputDependencies.from(inputDependencies) 50 | it.outputSourcesDir.set(outputSourcesDir) 51 | it.outputResourcesDir.set(outputResourcesDir) 52 | } 53 | 54 | workQueue.await() // I'm not sure if waiting is necessary, 55 | // but I suspect that the task dependencies aren't configured correctly, 56 | // so: better-safe-than-sorry. 57 | // Try removing await() when Benchmarks follows Gradle best practices. 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/JvmJavaTasks.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi 4 | import org.gradle.api.* 5 | import org.openjdk.jmh.util.Version 6 | 7 | @KotlinxBenchmarkPluginInternalApi 8 | fun Project.processJavaSourceSet(target: JavaBenchmarkTarget) { 9 | logger.info("Configuring benchmarks for '${target.name}' using Java") 10 | 11 | val sourceSet = target.sourceSet 12 | 13 | // get configure source set and add JMH core dependency to it 14 | this.configureJmhDependency(target) 15 | 16 | // we need JMH generator runtime configuration for each BenchmarkConfiguration since version can be different 17 | val workerClasspath = createJmhGenerationRuntimeConfiguration(target.name, target.jmhVersion) 18 | 19 | // Create a task that will process output bytecode and generate benchmark Java source code 20 | createJvmBenchmarkGenerateSourceTask( 21 | target, 22 | workerClasspath, 23 | sourceSet.compileClasspath, 24 | sourceSet.classesTaskName, 25 | sourceSet.output 26 | ) 27 | 28 | // Create a task that will compile generated Java source code into class files 29 | createJvmBenchmarkCompileTask(target, sourceSet.runtimeClasspath) 30 | 31 | // Create a task that will execute benchmark code 32 | target.extension.configurations.forEach { 33 | createJvmBenchmarkExecTask(it, target, sourceSet.runtimeClasspath) 34 | } 35 | } 36 | 37 | private fun Project.configureJmhDependency(target: JavaBenchmarkTarget) { 38 | checkJmhVersion(target) 39 | val dependencies = dependencies 40 | 41 | // Add dependency to JMH core library to the source set designated by config.name 42 | val jmhCore = dependencies.create("${BenchmarksPlugin.JMH_CORE_DEPENDENCY}:${target.jmhVersion}") 43 | val configurationRoot = "implementation" 44 | 45 | val dependencyConfiguration = if (target.name == "main") 46 | configurationRoot 47 | else 48 | "${target.name}${configurationRoot.capitalize()}" 49 | 50 | dependencies.add(dependencyConfiguration, jmhCore) 51 | } 52 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | # Note: Kotlin version can be overridden by passing `-Pkotlin_version=` 4 | kotlin = "2.0.20" 5 | kotlin-for-gradle-plugin = "2.0.20" # Kotlin 2.1 removes support for the used language version / api version: KT-60521 6 | kotlinx-binaryCompatibilityValidator = "0.16.2" 7 | kotlinx-teamInfra = "0.4.0-dev-85" 8 | squareup-kotlinpoet = "1.3.0" 9 | jmh = "1.37" 10 | gradle-pluginPublish = "1.3.1" 11 | 12 | # Note: This version can be overridden by passing `-Pmin_supported_gradle_version=` 13 | minSupportedGradle = "7.4" 14 | minSupportedKotlin = "2.0.0" 15 | 16 | [libraries] 17 | 18 | kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect" } 19 | 20 | kotlin-utilKlibMetadata = { module = "org.jetbrains.kotlin:kotlin-util-klib-metadata" } 21 | kotlin-utilKlib = { module = "org.jetbrains.kotlin:kotlin-util-klib" } 22 | kotlin-utilIo = { module = "org.jetbrains.kotlin:kotlin-util-io" } 23 | 24 | kotlin-compilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" } 25 | 26 | kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 27 | 28 | kotlinx-teamInfraGradlePlugin = { module = "kotlinx.team:kotlinx.team.infra", version.ref = "kotlinx-teamInfra" } 29 | 30 | squareup-kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "squareup-kotlinpoet" } 31 | 32 | jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } 33 | jmh-generatorBytecode = { module = "org.openjdk.jmh:jmh-generator-bytecode", version.ref = "jmh" } 34 | 35 | [plugins] 36 | 37 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 38 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 39 | kotlinx-binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "kotlinx-binaryCompatibilityValidator" } 40 | kotlinx-teamInfraGradlePlugin = { id = "kotlinx.team.infra", version.ref = "kotlinx-teamInfra" } 41 | 42 | gradle-pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "gradle-pluginPublish" } 43 | -------------------------------------------------------------------------------- /runtime/wasmJsMain/src/kotlinx/benchmark/D8EngineSupport.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlin.time.Duration.Companion.milliseconds 4 | 5 | @JsFun("(path) => globalThis.read(path)") 6 | private external fun d8ReadFile(path: String): String 7 | 8 | @JsFun("() => globalThis.arguments.join(' ')") 9 | private external fun d8Arguments(): String 10 | 11 | private const val maxStringLength = 65_536 / 4 12 | 13 | internal object D8EngineSupport : JsEngineSupport() { 14 | override fun writeFile(path: String, text: String) { 15 | //WORKAROUND: D8 cannot write into files, this format will be parsed on gradle plugin side 16 | if (text.isEmpty()) { 17 | print("") 18 | } else { 19 | print("") 20 | //TODO("Workaround for kotlin/wasm issue in 1.7.20 and below. This should be removed for kotlin 1.8.0 or above") 21 | var srcStartIndex = 0 22 | var srcEndIndex = srcStartIndex + maxStringLength 23 | while (srcEndIndex < text.length) { 24 | print(text.substring(srcStartIndex, srcEndIndex)) 25 | srcStartIndex = srcEndIndex 26 | srcEndIndex += maxStringLength 27 | } 28 | print(text.substring(srcStartIndex)) 29 | print("") 30 | } 31 | } 32 | 33 | override fun readFile(path: String): String = 34 | d8ReadFile(path) 35 | 36 | override fun arguments(): Array = 37 | d8Arguments().split(' ').toTypedArray() 38 | } 39 | 40 | @JsFun("() => (typeof self !== 'undefined' ? self : globalThis).performance") 41 | private external fun getPerformance(): ExternalInterfaceType 42 | 43 | @JsFun("(performance) => performance.now()") 44 | private external fun performanceNow(performance: ExternalInterfaceType): Double 45 | 46 | internal inline fun d8MeasureTime(block: () -> Unit): Long { 47 | val performance = getPerformance() 48 | val start = performanceNow(performance) 49 | block() 50 | val end = performanceNow(performance) 51 | val startInNs = start.milliseconds.inWholeNanoseconds 52 | val endInNs = end.milliseconds.inWholeNanoseconds 53 | return endInNs - startInNs 54 | } -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/JsSourceGeneratorTask.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi 4 | import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable 5 | import kotlinx.benchmark.gradle.internal.generator.workers.GenerateJsSourceWorker 6 | import org.gradle.api.* 7 | import org.gradle.api.file.* 8 | import org.gradle.api.tasks.* 9 | import org.gradle.workers.WorkerExecutor 10 | import java.io.File 11 | import javax.inject.Inject 12 | 13 | @CacheableTask 14 | abstract class JsSourceGeneratorTask 15 | @KotlinxBenchmarkPluginInternalApi 16 | @Inject 17 | constructor( 18 | private val workerExecutor: WorkerExecutor 19 | ) : DefaultTask() { 20 | 21 | @Input 22 | lateinit var title: String 23 | 24 | @Input 25 | var useBenchmarkJs: Boolean = true 26 | 27 | @Classpath 28 | lateinit var inputClassesDirs: FileCollection 29 | 30 | @Classpath 31 | lateinit var inputDependencies: FileCollection 32 | 33 | @OutputDirectory 34 | lateinit var outputResourcesDir: File 35 | 36 | @OutputDirectory 37 | lateinit var outputSourcesDir: File 38 | 39 | @get:Classpath 40 | abstract val runtimeClasspath: ConfigurableFileCollection 41 | 42 | @TaskAction 43 | fun generate() { 44 | val workQueue = workerExecutor.classLoaderIsolation { 45 | it.classpath.from(runtimeClasspath) 46 | } 47 | 48 | @OptIn(RequiresKotlinCompilerEmbeddable::class) 49 | workQueue.submit(GenerateJsSourceWorker::class.java) { 50 | it.title.set(title) 51 | it.inputClasses.from(inputClassesDirs) 52 | it.inputDependencies.from(inputDependencies) 53 | it.outputSourcesDir.set(outputSourcesDir) 54 | it.outputResourcesDir.set(outputResourcesDir) 55 | it.useBenchmarkJs.set(useBenchmarkJs) 56 | } 57 | 58 | workQueue.await() // I'm not sure if waiting is necessary, 59 | // but I suspect that the task dependencies aren't configured correctly, 60 | // so: better-safe-than-sorry. 61 | // Try removing await() when Benchmarks follows Gradle best practices. 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/ConsoleAndFilesOutputStream.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import java.io.ByteArrayOutputStream 4 | import java.io.FileOutputStream 5 | import java.io.OutputStream 6 | 7 | internal class ConsoleAndFilesOutputStream : OutputStream() { 8 | private val buffer = ByteArrayOutputStream() 9 | private var currentStream: OutputStream = System.out 10 | private val fileTag = " { 36 | buffer.writeTo(currentStream) 37 | buffer.reset() 38 | buffer.write(b) 39 | tagOpened = true 40 | } 41 | closeTag -> { 42 | if (tagOpened) { 43 | buffer.write(b) 44 | processTag(buffer.toString()) 45 | tagOpened = false 46 | } else { 47 | buffer.write(b) 48 | } 49 | } 50 | else -> { 51 | buffer.write(b) 52 | } 53 | } 54 | } 55 | 56 | override fun flush() { 57 | buffer.flush() 58 | buffer.writeTo(currentStream) 59 | buffer.reset() 60 | currentStream.flush() 61 | } 62 | 63 | override fun close() { 64 | flush() 65 | buffer.close() 66 | currentStream.close() 67 | } 68 | } -------------------------------------------------------------------------------- /runtime/wasmJsMain/src/kotlinx/benchmark/NodeJsEngineSupport.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlin.time.DurationUnit 4 | import kotlin.time.toDuration 5 | 6 | @JsFun(""" 7 | (globalThis.module = (typeof process !== 'undefined') && (process.release.name === 'node') ? 8 | await import(/* webpackIgnore: true */'node:module') : void 0, () => {}) 9 | """) 10 | internal external fun persistModule() 11 | 12 | @JsFun("""() => { 13 | const importMeta = import.meta; 14 | return globalThis.module.default.createRequire(importMeta.url); 15 | } 16 | """) 17 | internal external fun getRequire(): JsAny 18 | 19 | @JsFun("(require, path, text) => require('fs').writeFileSync(path, text, 'utf8')") 20 | private external fun nodeJsWriteFile(require: JsAny, path: String, text: String) 21 | 22 | @JsFun("(require, path, text) => require('fs').readFileSync(path, 'utf8')") 23 | private external fun nodeJsReadFile(require: JsAny, path: String): String 24 | 25 | @JsFun("() => process.argv.slice(2).join(' ')") 26 | private external fun nodeJsArguments(): String 27 | 28 | internal object NodeJsEngineSupport : JsEngineSupport() { 29 | private val require = persistModule().let { getRequire() } 30 | 31 | override fun writeFile(path: String, text: String) = 32 | nodeJsWriteFile(require, path, text) 33 | 34 | override fun readFile(path: String): String = 35 | nodeJsReadFile(require, path) 36 | 37 | override fun arguments(): Array = 38 | nodeJsArguments().split(' ').toTypedArray() 39 | } 40 | 41 | private fun hrTimeToNs(hrTime: ExternalInterfaceType): Long { 42 | val fromSeconds = getArrayElement(hrTime, 0).toDuration(DurationUnit.SECONDS) 43 | val fromNanos = getArrayElement(hrTime, 1).toDuration(DurationUnit.NANOSECONDS) 44 | return (fromSeconds + fromNanos).inWholeNanoseconds 45 | } 46 | 47 | @JsFun("() => process") 48 | private external fun getProcess(): ExternalInterfaceType 49 | 50 | @JsFun("(process) => process.hrtime()") 51 | private external fun getHrTime(process: ExternalInterfaceType): ExternalInterfaceType 52 | 53 | @JsFun("(array, i) => array[i]") 54 | private external fun getArrayElement(array: ExternalInterfaceType, i: Int): Double 55 | 56 | internal inline fun nodeJsMeasureTime(block: () -> Unit): Long { 57 | val process = getProcess() 58 | val start = getHrTime(process) 59 | block() 60 | val end = getHrTime(process) 61 | return hrTimeToNs(end) - hrTimeToNs(start) 62 | } -------------------------------------------------------------------------------- /runtime/nativeMain/src/kotlinx/benchmark/Utils.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.native.NativeExecutor 4 | import kotlinx.cinterop.* 5 | import platform.posix.* 6 | 7 | internal actual fun Double.format(precision: Int, useGrouping: Boolean): String { 8 | val longPart = toLong() 9 | val fractional = this - longPart 10 | val thousands = 11 | if (useGrouping) longPart.toString().replace(Regex("\\B(?=(\\d{3})+(?!\\d))"), ",") 12 | else longPart.toString() 13 | if (precision == 0) 14 | return thousands 15 | 16 | return memScoped { 17 | val bytes = allocArray(100) 18 | sprintf(bytes, "%.${precision}f", fractional) 19 | val fractionText = bytes.toKString() 20 | thousands + fractionText.removePrefix("0") 21 | } 22 | } 23 | 24 | internal actual fun String.writeFile(text: String) { 25 | val file = fopen(this, "w") 26 | try { 27 | if (fputs(text, file) == EOF) throw Error("File write error") 28 | } finally { 29 | fclose(file) 30 | } 31 | } 32 | 33 | internal actual fun String.readFile(): String = buildString { 34 | val file = fopen(this@readFile, "rb") 35 | try { 36 | memScoped { 37 | while (true) { 38 | val bufferLength = 64 * 1024 39 | val buffer = allocArray(bufferLength) 40 | val line = fgets(buffer, bufferLength, file)?.toKString() // newline symbol is included 41 | if (line.isNullOrEmpty()) break 42 | append(line) 43 | } 44 | } 45 | } finally { 46 | fclose(file) 47 | } 48 | } 49 | 50 | internal fun String.parseBenchmarkConfig(): NativeExecutor.BenchmarkRun { 51 | fun String.getElement(name: String) = 52 | if (startsWith(name)) { 53 | substringAfter("$name: ") 54 | } else throw NoSuchElementException("Parameter `$name` is required.") 55 | 56 | val content = readFile() 57 | val lines = content.lines().filter { it.isNotEmpty() } 58 | require(lines.size == 3) { "Wrong format of detailed benchmark configuration file. " } 59 | val name = lines[0].getElement("benchmark") 60 | val configuration = BenchmarkConfiguration.parse(lines[1].getElement("configuration")) 61 | val parameters = lines[2].getElement("parameters").parseMap() 62 | return NativeExecutor.BenchmarkRun(name, configuration, parameters) 63 | } 64 | 65 | internal actual inline fun measureNanoseconds(block: () -> Unit): Long = TODO("Not implemented for this platform") -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | One can contribute to the project by reporting issues or submitting changes via pull request. 4 | 5 | ## Reporting issues 6 | 7 | Please use [GitHub issues](https://github.com/Kotlin/kotlinx-benchmark/issues) for filing feature requests and bug reports. 8 | 9 | Questions about usage and general inquiries are better suited for StackOverflow or the #benchmarks channel in KotlinLang Slack. 10 | 11 | ## Submitting changes 12 | 13 | Submit pull requests [here](https://github.com/Kotlin/kotlinx-benchmark/pulls). 14 | However, please keep in mind that maintainers will have to support the resulting code of the project, 15 | so do familiarize yourself with the following guidelines. 16 | 17 | * All development (both new features and bug fixes) is performed in the `master` branch. 18 | * Base your PRs against the `master` branch. 19 | * If you make any code changes: 20 | * Follow the [Kotlin Coding Conventions](https://kotlinlang.org/docs/reference/coding-conventions.html). 21 | * Use 4 spaces for indentation. 22 | * Use imports with '*'. 23 | * [Build the project](#building) to make sure it all works and passes the tests. 24 | * If you fix a bug: 25 | * Write the test that reproduces the bug. 26 | * Depending on a particular bug, it may require either a unit or an integration test. For the latter, please check [integration module](integration/src/test) for examples. 27 | * Fixes without tests are accepted only in exceptional circumstances if it can be shown that writing the 28 | corresponding test is too hard or otherwise impractical. 29 | * Follow the style of writing tests that is used in this project: 30 | name test functions as `testXxx`. Don't use backticks in test names. 31 | * Comment on the existing issue if you want to work on it. Ensure that the issue not only describes a problem, but also describes a solution that has received positive feedback. Propose a solution if none has been suggested. 32 | 33 | ## Building 34 | 35 | This project is built with Gradle. 36 | 37 | * Run `./gradlew build` to build. It also runs all the tests. 38 | 39 | You can import this project into IDEA, but you have to delegate build actions 40 | to Gradle (in Preferences -> Build, Execution, Deployment -> Build Tools -> Gradle -> Build and run). 41 | 42 | ### Updating the public API dump 43 | 44 | * Use the [Binary Compatibility Validator](https://github.com/Kotlin/binary-compatibility-validator/blob/master/README.md) for updates to public API: 45 | * Run `./gradlew apiDump` to update API index files. 46 | * Commit the updated API indexes together with other changes. 47 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/SupportedGradleVersionTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.gradle.util.GradleVersion 4 | import kotlin.test.Test 5 | import kotlin.test.assertEquals 6 | import kotlin.test.assertTrue 7 | 8 | class SupportedGradleVersionTest : GradleTest() { 9 | 10 | /** The min supported version used in build scripts, provided as a system property. */ 11 | private val minSupportedGradleVersion = System.getProperty("minSupportedGradleVersion") 12 | private val unsupportedGradleVersionWarningMessage = 13 | "JetBrains Gradle Benchmarks plugin requires Gradle version ${GradleTestVersion.MinSupportedGradleVersion.versionString}" 14 | private val incompatibleKotlinAndGradleVersionsErrorMessage = 15 | "The applied Kotlin Gradle is not compatible with the used Gradle version (Gradle ${GradleTestVersion.UnsupportedGradleVersion.versionString})" 16 | 17 | @Test 18 | fun `test MinSupportedGradleVersion matches the version used in build scripts`() { 19 | assertEquals(minSupportedGradleVersion, GradleTestVersion.MinSupportedGradleVersion.versionString) 20 | } 21 | 22 | @Test 23 | fun `test MinSupportedGradleVersion is greater than UnsupportedGradleVersion`() { 24 | // verify the test data is valid 25 | assertTrue( 26 | GradleVersion.version(GradleTestVersion.MinSupportedGradleVersion.versionString) > 27 | GradleVersion.version(GradleTestVersion.UnsupportedGradleVersion.versionString) 28 | ) 29 | } 30 | 31 | @Test 32 | fun `when using min supported Gradle version, expect no warning`() { 33 | val runner = project("kotlin-multiplatform", gradleVersion = GradleTestVersion.MinSupportedGradleVersion) 34 | 35 | runner.runAndSucceed(":help", "-q") { 36 | assertOutputDoesNotContain(unsupportedGradleVersionWarningMessage) 37 | } 38 | } 39 | 40 | @Test 41 | fun `when using unsupported Gradle version, expect warning`() { 42 | val runner = project("kotlin-multiplatform", gradleVersion = GradleTestVersion.UnsupportedGradleVersion) 43 | 44 | runner.run(":help", "-q") { 45 | assertTrue( 46 | // When kotlinx-benchmark-plugin has the same minimum supported Gradle version as KGP, reported by KGP 47 | output.contains(incompatibleKotlinAndGradleVersionsErrorMessage) || 48 | // When kotlinx-benchmark-plugin has a newer minimum supported Gradle version than KGP, reported by kxb 49 | output.contains(unsupportedGradleVersionWarningMessage) 50 | ) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/WasmMultiplatformTasks.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi 4 | import org.gradle.api.* 5 | import org.jetbrains.kotlin.gradle.targets.js.dsl.* 6 | import org.jetbrains.kotlin.gradle.targets.js.ir.* 7 | 8 | @KotlinxBenchmarkPluginInternalApi 9 | fun Project.processWasmCompilation(target: WasmBenchmarkTarget) { 10 | project.logger.info("Configuring benchmarks for '${target.name}' using Kotlin/Wasm") 11 | val compilation = target.compilation 12 | 13 | createWasmBenchmarkGenerateSourceTask(target, compilation) 14 | 15 | val benchmarkCompilation = createWasmBenchmarkCompileTask(target) 16 | 17 | target.extension.configurations.forEach { 18 | createJsEngineBenchmarkExecTask(it, target, benchmarkCompilation) 19 | } 20 | } 21 | 22 | private fun Project.createWasmBenchmarkCompileTask(target: WasmBenchmarkTarget): KotlinJsIrCompilation { 23 | val compilation = target.compilation 24 | val benchmarkBuildDir = benchmarkBuildDir(target) 25 | val benchmarkCompilation = 26 | compilation.target.compilations.create(target.name + BenchmarksPlugin.BENCHMARK_COMPILATION_SUFFIX) as KotlinJsIrCompilation 27 | 28 | val kotlinTarget = compilation.target 29 | check(kotlinTarget is KotlinJsTargetDsl) 30 | 31 | kotlinTarget.binaries.executable(benchmarkCompilation) 32 | 33 | benchmarkCompilation.apply { 34 | val sourceSet = kotlinSourceSets.single() 35 | 36 | sourceSet.resources.setSrcDirs(files()) 37 | sourceSet.kotlin.setSrcDirs(files("$benchmarkBuildDir/sources")) 38 | 39 | associateWith(compilation) 40 | 41 | compileTaskProvider.configure { 42 | it.apply { 43 | group = BenchmarksPlugin.BENCHMARKS_TASK_GROUP 44 | description = "Compile Wasm benchmark source files for '${target.name}'" 45 | dependsOn("${target.name}${BenchmarksPlugin.BENCHMARK_GENERATE_SUFFIX}") 46 | } 47 | } 48 | } 49 | return benchmarkCompilation 50 | } 51 | 52 | private fun Project.createWasmBenchmarkGenerateSourceTask( 53 | target: WasmBenchmarkTarget, 54 | compilationOutput: KotlinJsIrCompilation 55 | ) { 56 | val benchmarkBuildDir = benchmarkBuildDir(target) 57 | task("${target.name}${BenchmarksPlugin.BENCHMARK_GENERATE_SUFFIX}") { 58 | group = BenchmarksPlugin.BENCHMARKS_TASK_GROUP 59 | description = "Generate Wasm source files for '${target.name}'" 60 | title = target.name 61 | inputClassesDirs = compilationOutput.output.classesDirs 62 | inputDependencies = compilationOutput.runtimeDependencyFiles 63 | outputResourcesDir = file("$benchmarkBuildDir/resources") 64 | outputSourcesDir = file("$benchmarkBuildDir/sources") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docs/tasks-overview.md: -------------------------------------------------------------------------------- 1 | # Overview of Tasks Provided by kotlinx-benchmark Gradle Plugin 2 | 3 | The kotlinx-benchmark plugin creates different Gradle tasks depending on how it is configured. 4 | For each pair of configuration profile and registered target a task is created to execute that profile on the respective platform. 5 | To learn more about configuration profiles, refer to [configuration-options.md](configuration-options.md). 6 | 7 | ## Example Configuration 8 | 9 | To illustrate, consider the following `kotlinx-benchmark` configuration: 10 | 11 | ```kotlin 12 | // build.gradle.kts 13 | benchmark { 14 | configurations { 15 | named("main") { 16 | iterations = 20 17 | warmups = 20 18 | iterationTime = 1 19 | iterationTimeUnit = "s" 20 | } 21 | register("smoke") { 22 | include("Essential") 23 | iterations = 10 24 | warmups = 10 25 | iterationTime = 200 26 | iterationTimeUnit = "ms" 27 | } 28 | } 29 | 30 | targets { 31 | register("jvm") 32 | register("js") 33 | } 34 | } 35 | ``` 36 | 37 | ## Tasks for the "main" Configuration Profile 38 | 39 | - **`benchmark`**: 40 | - Runs benchmarks within the "main" profile for all registered targets. 41 | - In our example, `benchmark` runs benchmarks within the "main" profile in both `jvm` and `js` targets. 42 | 43 | - **`Benchmark`**: 44 | - Runs benchmarks within the "main" profile for a particular target. 45 | - In our example, `jvmBenchmark` runs benchmarks within the "main" profile in the `jvm` target, while `jsBenchmark` runs them in the `js` target. 46 | 47 | ## Tasks for Custom Configuration Profiles 48 | 49 | - **`Benchmark`**: 50 | - Runs benchmarks within `` profile in all registered targets. 51 | - In our example, `smokeBenchmark` runs benchmarks within the "smoke" profile. 52 | 53 | - **`Benchmark`**: 54 | - Runs benchmarks within `` profile in `` target. 55 | - In our example, `jvmSmokeBenchmark` runs benchmarks within the "smoke" profile in `jvm` target while `jsSmokeBenchmark` runs them in `js` target. 56 | 57 | ## Other useful tasks 58 | 59 | - **`BenchmarkJar`**: 60 | - Created only when a Kotlin/JVM target is registered for benchmarking. 61 | - Produces a self-contained executable JAR file in `build/benchmarks//jars/` directory of your project that contains your benchmarks in `` target, and all essential JMH infrastructure code. 62 | - The JAR file can be run using `java -jar path-to-the.jar` command with relevant options. Run with `-h` to see the available options. 63 | - The JAR file can be used for running JMH profilers. 64 | - In our example, `jvmBenchmarkJar` produces a JAR file in `build/benchmarks/jvm/jars/` directory that contains benchmarks in `jvm` target. 65 | -------------------------------------------------------------------------------- /integration/src/test/kotlin/kotlinx/benchmark/integration/ConfigurationCacheTest.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | import kotlin.test.* 5 | 6 | class ConfigurationCacheTest : GradleTest() { 7 | private fun runConfigurationCacheTest(projectName: String, invokedTasks: List, executedTasks: List) { 8 | val project = project(projectName, gradleVersion = GradleTestVersion.v8_0) { 9 | // this test doesn't pass for K/N on Gradle 8.1 yet: https://youtrack.jetbrains.com/issue/KT-58063 10 | configuration("main") { 11 | warmups = 1 12 | iterations = 1 13 | iterationTime = 100 14 | iterationTimeUnit = "ms" 15 | advanced("jmhIgnoreLock", true) 16 | } 17 | } 18 | 19 | project.runAndSucceed(*invokedTasks.toTypedArray(), "--configuration-cache") { 20 | assertTasksExecuted(invokedTasks + executedTasks) 21 | assertConfigurationCacheStored() 22 | } 23 | project.runAndSucceed("clean", "--configuration-cache") { 24 | assertConfigurationCacheStored() 25 | } 26 | project.runAndSucceed(*invokedTasks.toTypedArray(), "--configuration-cache") { 27 | assertTasksExecuted(invokedTasks + executedTasks) 28 | assertConfigurationCacheReused() 29 | } 30 | project.runAndSucceed(*invokedTasks.toTypedArray(), "--configuration-cache") { 31 | assertTasksUpToDate(executedTasks) 32 | assertConfigurationCacheReused() 33 | } 34 | } 35 | 36 | @Test 37 | fun testConfigurationCacheNative() = runConfigurationCacheTest( 38 | "kotlin-multiplatform", 39 | listOf(":nativeBenchmark"), 40 | listOf(":compileKotlinNative", ":nativeBenchmarkGenerate", ":compileNativeBenchmarkKotlinNative", ":linkNativeBenchmarkDebugExecutableNative") 41 | ) 42 | 43 | @Test 44 | fun testConfigurationCacheJs() = runConfigurationCacheTest( 45 | "kotlin-multiplatform", 46 | listOf(":jsBenchmark"), 47 | listOf(":compileKotlinJs", ":jsBenchmarkGenerate", ":compileJsBenchmarkProductionExecutableKotlinJs") 48 | ) 49 | 50 | @Test 51 | fun testConfigurationCacheJvm() = runConfigurationCacheTest( 52 | "kotlin-multiplatform", 53 | listOf(":jvmBenchmark"), 54 | listOf(":compileKotlinJvm", ":jvmBenchmarkGenerate", ":jvmBenchmarkCompile") 55 | ) 56 | 57 | @Test 58 | fun testConfigurationCacheWasm() = runConfigurationCacheTest( 59 | "kotlin-multiplatform", 60 | listOf(":wasmJsBenchmark"), 61 | listOf(":compileKotlinWasmJs", ":wasmJsBenchmarkGenerate", ":compileWasmJsBenchmarkProductionExecutableKotlinWasmJs") 62 | ) 63 | } 64 | 65 | private fun BuildResult.assertConfigurationCacheStored() { 66 | assertOutputContains("Configuration cache entry stored.") 67 | } 68 | 69 | private fun BuildResult.assertConfigurationCacheReused() { 70 | assertOutputContains("Configuration cache entry reused.") 71 | } 72 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/BenchmarkConfiguration.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | @KotlinxBenchmarkRuntimeInternalApi 6 | class BenchmarkConfiguration private constructor( 7 | val iterations: Int, 8 | val warmups: Int, 9 | val iterationTime: Long, 10 | val iterationTimeUnit: BenchmarkTimeUnit, 11 | val outputTimeUnit: BenchmarkTimeUnit, 12 | val mode: Mode, 13 | val advanced: Map, 14 | ) { 15 | constructor(runner: RunnerConfiguration, suite: SuiteDescriptor<*>) : this( 16 | iterations = runner.iterations ?: suite.iterations, 17 | warmups = runner.warmups ?: suite.warmups, 18 | iterationTime = runner.iterationTime ?: suite.iterationTime.value, 19 | iterationTimeUnit = runner.iterationTimeUnit ?: suite.iterationTime.timeUnit, 20 | outputTimeUnit = runner.outputTimeUnit ?: suite.outputTimeUnit, 21 | mode = runner.mode ?: suite.mode, 22 | advanced = runner.advanced 23 | ) 24 | 25 | override fun toString() = 26 | "iterations=$iterations, warmups=$warmups, iterationTime=$iterationTime, " + 27 | "iterationTimeUnit=${iterationTimeUnit.toText()}, outputTimeUnit=${outputTimeUnit.toText()}, " + 28 | "mode=${mode.toText()}" + 29 | advanced.entries.joinToString(prefix = ", ", separator = ", ") { "advanced:${it.key}=${it.value}" } 30 | 31 | @KotlinxBenchmarkRuntimeInternalApi 32 | companion object { 33 | fun parse(description: String): BenchmarkConfiguration { 34 | val parameters = description.parseMap() 35 | fun getParameterValue(key: String) = 36 | parameters[key] ?: throw NoSuchElementException("Parameter `$key` is required.") 37 | 38 | val advanced = parameters 39 | .filter { it.key.startsWith("advanced:") } 40 | .entries 41 | .associate { 42 | val advancedKey = it.key.substringAfter(":") 43 | check(advancedKey.isNotEmpty()) { "Invalid advanced key - should not be empty" } 44 | advancedKey to it.value 45 | } 46 | 47 | return BenchmarkConfiguration( 48 | iterations = getParameterValue("iterations").toInt(), 49 | warmups = getParameterValue("warmups").toInt(), 50 | iterationTime = getParameterValue("iterationTime").toLong(), 51 | iterationTimeUnit = parseTimeUnit(getParameterValue("iterationTimeUnit")), 52 | outputTimeUnit = parseTimeUnit(getParameterValue("outputTimeUnit")), 53 | mode = getParameterValue("mode").toMode(), 54 | advanced = advanced 55 | ) 56 | } 57 | } 58 | } 59 | 60 | internal fun String.parseMap(): Map = 61 | removeSurrounding("{", "}") 62 | .split(", ") 63 | .filter { it.isNotEmpty() } 64 | .associate { 65 | val keyValue = it.split("=") 66 | require(keyValue.size == 2) { "Wrong format of map string description!" } 67 | val (key, value) = keyValue 68 | key to value 69 | } -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/internal/BenchmarkDependencies.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle.internal 2 | 3 | import kotlinx.benchmark.gradle.BenchmarksExtension 4 | import org.gradle.api.* 5 | import org.gradle.api.artifacts.* 6 | import org.gradle.api.attributes.* 7 | import org.gradle.api.attributes.Usage.* 8 | import org.gradle.api.model.* 9 | import org.gradle.util.GradleVersion 10 | 11 | /** 12 | * Utility for containing all Gradle [Configuration]s used by Benchmarks. 13 | */ 14 | internal class BenchmarkDependencies( 15 | project: Project, 16 | benchmarksExtension: BenchmarksExtension, 17 | ) { 18 | private val objects: ObjectFactory = project.objects 19 | 20 | private val benchmarkGenerator: Configuration = 21 | project.configurations.create("benchmarkGenerator") { 22 | it.description = 23 | "Internal kotlinx-benchmark Configuration. Contains declared dependencies required for running benchmark generators." 24 | it.declarable() 25 | 26 | it.defaultDependencies { deps -> 27 | deps.addLater( 28 | benchmarksExtension.kotlinCompilerVersion.map { version -> 29 | project.dependencies.create("org.jetbrains.kotlin:kotlin-compiler-embeddable:$version") 30 | } 31 | ) 32 | } 33 | } 34 | 35 | val benchmarkGeneratorResolver: Configuration = 36 | project.configurations.create("benchmarkGenerator.resolver") { 37 | // The name has a dot, to prevent Gradle from generating a Kotlin DSL accessor. 38 | it.description = 39 | "Internal kotlinx-benchmark Configuration. Resolves dependencies required for running benchmark generators." 40 | it.resolvable() 41 | 42 | it.extendsFrom(benchmarkGenerator) 43 | 44 | it.attributes { atts -> 45 | atts.attribute(USAGE_ATTRIBUTE, objects.named(Usage::class.java, JAVA_RUNTIME)) 46 | } 47 | } 48 | 49 | internal companion object { 50 | 51 | /** Mark this [Configuration] as one that will be used to declare dependencies. */ 52 | private fun Configuration.declarable() { 53 | isCanBeDeclaredCompat = true 54 | isCanBeResolved = false 55 | isCanBeConsumed = false 56 | isVisible = false 57 | } 58 | 59 | /** Mark this [Configuration] as one that will be used to resolve dependencies. */ 60 | private fun Configuration.resolvable() { 61 | isCanBeDeclaredCompat = false 62 | isCanBeResolved = true 63 | isCanBeConsumed = false 64 | isVisible = false 65 | } 66 | 67 | @Suppress("UnstableApiUsage") 68 | /** `true` if [Configuration.isCanBeDeclared] is supported by the current Gradle version. */ 69 | private val isCanBeDeclaredSupported = GradleVersion.current() >= GradleVersion.version("8.2") 70 | 71 | // Remove when minimum supported Gradle version is >= 8.2 72 | @Suppress("UnstableApiUsage") 73 | private var Configuration.isCanBeDeclaredCompat: Boolean 74 | get() = if (isCanBeDeclaredSupported) isCanBeDeclared else false 75 | set(value) { 76 | if (isCanBeDeclaredSupported) isCanBeDeclared = value 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/internal/generator/workers/GenerateWasmSourceWorker.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle.internal.generator.workers 2 | 3 | import kotlinx.benchmark.gradle.KlibResolver 4 | import kotlinx.benchmark.gradle.Platform 5 | import kotlinx.benchmark.gradle.SuiteSourceGenerator 6 | import kotlinx.benchmark.gradle.createModuleDescriptor 7 | import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable 8 | import org.gradle.api.file.* 9 | import org.gradle.api.provider.* 10 | import org.gradle.workers.WorkAction 11 | import org.gradle.workers.WorkParameters 12 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 13 | import org.jetbrains.kotlin.storage.LockBasedStorageManager 14 | import org.jetbrains.kotlin.storage.StorageManager 15 | import java.io.File 16 | 17 | /** 18 | * Generates Wasm benchmarking source code. 19 | * 20 | * This worker requires `kotlin-compiler-embeddable` and *must* be run in an isolated classpath. 21 | * 22 | * @see kotlinx.benchmark.gradle.WasmSourceGeneratorTask 23 | */ 24 | @RequiresKotlinCompilerEmbeddable 25 | internal abstract class GenerateWasmSourceWorker : WorkAction { 26 | 27 | internal interface Params : WorkParameters { 28 | val title: Property 29 | val inputClasses: ConfigurableFileCollection 30 | val inputDependencies: ConfigurableFileCollection 31 | val outputSourcesDir: DirectoryProperty 32 | val outputResourcesDir: DirectoryProperty 33 | } 34 | 35 | override fun execute() { 36 | 37 | val title = parameters.title.get() 38 | val inputDependencies = parameters.inputDependencies.files 39 | val outputSourcesDir = parameters.outputSourcesDir.get().asFile 40 | 41 | parameters.outputSourcesDir.get().asFile.deleteRecursively() 42 | parameters.outputResourcesDir.get().asFile.deleteRecursively() 43 | 44 | parameters.inputClasses.forEach { lib: File -> 45 | generateSources( 46 | title = title, 47 | lib = lib, 48 | inputDependencies = inputDependencies, 49 | outputSourcesDir = outputSourcesDir, 50 | ) 51 | } 52 | } 53 | 54 | private fun generateSources( 55 | title: String, 56 | lib: File, 57 | inputDependencies: Set, 58 | outputSourcesDir: File, 59 | ) { 60 | val modules = loadIr( 61 | lib, 62 | inputDependencies = inputDependencies, 63 | LockBasedStorageManager("Inspect"), 64 | ) 65 | modules.forEach { module -> 66 | val generator = SuiteSourceGenerator( 67 | title, 68 | module, 69 | outputSourcesDir, 70 | Platform.WasmBuiltIn 71 | ) 72 | generator.generate() 73 | } 74 | } 75 | 76 | private fun loadIr( 77 | lib: File, 78 | inputDependencies: Set, 79 | storageManager: StorageManager, 80 | ): List { 81 | //skip processing of empty dirs (fail if not to do it) 82 | if (lib.listFiles() == null) return emptyList() 83 | val dependencies = inputDependencies.filterNot { it.extension == "js" }.toSet() 84 | val module = KlibResolver.JS.createModuleDescriptor(lib, dependencies, storageManager) 85 | return listOf(module) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/internal/generator/workers/GenerateJsSourceWorker.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle.internal.generator.workers 2 | 3 | import kotlinx.benchmark.gradle.KlibResolver 4 | import kotlinx.benchmark.gradle.Platform 5 | import kotlinx.benchmark.gradle.SuiteSourceGenerator 6 | import kotlinx.benchmark.gradle.createModuleDescriptor 7 | import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable 8 | import org.gradle.api.file.* 9 | import org.gradle.api.provider.* 10 | import org.gradle.workers.WorkAction 11 | import org.gradle.workers.WorkParameters 12 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 13 | import org.jetbrains.kotlin.storage.LockBasedStorageManager 14 | import org.jetbrains.kotlin.storage.StorageManager 15 | import java.io.File 16 | 17 | /** 18 | * Generates JavaScript benchmarking source code. 19 | * 20 | * This worker requires `kotlin-compiler-embeddable` and *must* be run in an isolated classpath. 21 | * 22 | * @see kotlinx.benchmark.gradle.JsSourceGeneratorTask 23 | */ 24 | @RequiresKotlinCompilerEmbeddable 25 | internal abstract class GenerateJsSourceWorker : WorkAction { 26 | 27 | internal interface Params : WorkParameters { 28 | val title: Property 29 | val inputClasses: ConfigurableFileCollection 30 | val inputDependencies: ConfigurableFileCollection 31 | val outputSourcesDir: DirectoryProperty 32 | val outputResourcesDir: DirectoryProperty 33 | val useBenchmarkJs: Property 34 | } 35 | 36 | override fun execute() { 37 | parameters.outputSourcesDir.get().asFile.deleteRecursively() 38 | parameters.outputResourcesDir.get().asFile.deleteRecursively() 39 | 40 | parameters.inputClasses.forEach { lib: File -> 41 | generateSources( 42 | title = parameters.title.get(), 43 | lib = lib, 44 | inputDependencies = parameters.inputDependencies.files, 45 | outputSourcesDir = parameters.outputSourcesDir.get().asFile, 46 | useBenchmarkJs = parameters.useBenchmarkJs.get(), 47 | ) 48 | } 49 | } 50 | 51 | private fun generateSources( 52 | title: String, 53 | lib: File, 54 | inputDependencies: Set, 55 | outputSourcesDir: File, 56 | useBenchmarkJs: Boolean, 57 | ) { 58 | val modules = loadIr( 59 | lib = lib, 60 | inputDependencies = inputDependencies, 61 | storageManager = LockBasedStorageManager("Inspect"), 62 | ) 63 | modules.forEach { module -> 64 | val generator = SuiteSourceGenerator( 65 | title, 66 | module, 67 | outputSourcesDir, 68 | if (useBenchmarkJs) Platform.JsBenchmarkJs else Platform.JsBuiltIn 69 | ) 70 | generator.generate() 71 | } 72 | } 73 | 74 | private fun loadIr( 75 | lib: File, 76 | inputDependencies: Set, 77 | storageManager: StorageManager, 78 | ): List { 79 | // skip processing of empty dirs (fails if not to do it) 80 | if (lib.listFiles() == null) return emptyList() 81 | val dependencies = inputDependencies.filterNot { it.extension == "js" }.toSet() 82 | val module = KlibResolver.JS.createModuleDescriptor(lib, dependencies, storageManager) 83 | return listOf(module) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.teamcity/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | TeamCity Config DSL Script 5 | kotlinx.team.infra 6 | teamcity 7 | 1.0-SNAPSHOT 8 | 9 | 10 | org.jetbrains.teamcity 11 | configs-dsl-kotlin-parent 12 | 1.0-SNAPSHOT 13 | 14 | 15 | 16 | 17 | jetbrains-all 18 | https://download.jetbrains.com/teamcity-repository 19 | 20 | true 21 | 22 | 23 | 24 | teamcity-server 25 | https://teamcity.jetbrains.com/app/dsl-plugins-repository 26 | 27 | true 28 | 29 | 30 | 31 | 32 | 33 | 34 | JetBrains 35 | https://download.jetbrains.com/teamcity-repository 36 | 37 | 38 | 39 | 40 | . 41 | 42 | 43 | kotlin-maven-plugin 44 | org.jetbrains.kotlin 45 | ${kotlin.version} 46 | 47 | 48 | 49 | 50 | compile 51 | process-sources 52 | 53 | compile 54 | 55 | 56 | 57 | test-compile 58 | process-test-sources 59 | 60 | test-compile 61 | 62 | 63 | 64 | 65 | 66 | org.jetbrains.teamcity 67 | teamcity-configs-maven-plugin 68 | ${teamcity.dsl.version} 69 | 70 | kotlin 71 | target/generated-configs 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.jetbrains.teamcity 80 | configs-dsl-kotlin-latest 81 | ${teamcity.dsl.version} 82 | compile 83 | 84 | 85 | org.jetbrains.teamcity 86 | configs-dsl-kotlin-plugins-latest 87 | 1.0-SNAPSHOT 88 | pom 89 | compile 90 | 91 | 92 | org.jetbrains.kotlin 93 | kotlin-stdlib-jdk8 94 | ${kotlin.version} 95 | compile 96 | 97 | 98 | org.jetbrains.kotlin 99 | kotlin-script-runtime 100 | ${kotlin.version} 101 | compile 102 | 103 | 104 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/CommonBenchmarkAnnotations.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | @Target(AnnotationTarget.FUNCTION) 6 | expect annotation class Setup() 7 | 8 | @Target(AnnotationTarget.FUNCTION) 9 | expect annotation class TearDown() 10 | 11 | @Target(AnnotationTarget.FUNCTION) 12 | expect annotation class Benchmark() 13 | 14 | @Target(AnnotationTarget.CLASS) 15 | expect annotation class State(val value: Scope) 16 | 17 | expect enum class Scope { 18 | Benchmark 19 | } 20 | 21 | @Target(AnnotationTarget.CLASS) 22 | expect annotation class BenchmarkMode(vararg val value: Mode) 23 | 24 | expect enum class Mode { 25 | Throughput, AverageTime 26 | } 27 | 28 | @Target(AnnotationTarget.CLASS) 29 | expect annotation class OutputTimeUnit(val value: BenchmarkTimeUnit) 30 | 31 | expect enum class BenchmarkTimeUnit { 32 | NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES 33 | } 34 | 35 | @KotlinxBenchmarkRuntimeInternalApi 36 | @Suppress("REDUNDANT_ELSE_IN_WHEN") 37 | fun BenchmarkTimeUnit.toText() = when (this) { 38 | BenchmarkTimeUnit.NANOSECONDS -> "ns" 39 | BenchmarkTimeUnit.MICROSECONDS -> "us" 40 | BenchmarkTimeUnit.MILLISECONDS -> "ms" 41 | BenchmarkTimeUnit.SECONDS -> "sec" 42 | BenchmarkTimeUnit.MINUTES -> "min" 43 | else -> throw UnsupportedOperationException("$this is not supported") 44 | } 45 | 46 | @KotlinxBenchmarkRuntimeInternalApi 47 | fun String.toMode() = 48 | when (this) { 49 | "thrpt", "Throughput" -> Mode.Throughput 50 | "avgt", "AverageTime" -> Mode.AverageTime 51 | else -> throw UnsupportedOperationException("$this is not supported") 52 | } 53 | 54 | 55 | @KotlinxBenchmarkRuntimeInternalApi 56 | @Suppress("REDUNDANT_ELSE_IN_WHEN") 57 | fun Mode.toText() = when (this) { 58 | Mode.Throughput -> "thrpt" 59 | Mode.AverageTime -> "avgt" 60 | else -> throw UnsupportedOperationException("$this is not supported") 61 | } 62 | 63 | @KotlinxBenchmarkRuntimeInternalApi 64 | @Suppress("REDUNDANT_ELSE_IN_WHEN") 65 | fun BenchmarkTimeUnit.toMultiplier() = when (this) { 66 | BenchmarkTimeUnit.NANOSECONDS -> 1 67 | BenchmarkTimeUnit.MICROSECONDS -> 1_000 68 | BenchmarkTimeUnit.MILLISECONDS -> 1_000_000 69 | BenchmarkTimeUnit.SECONDS -> 1_000_000_000 70 | BenchmarkTimeUnit.MINUTES -> 60_000_000_000 71 | else -> throw UnsupportedOperationException("$this is not supported") 72 | } 73 | 74 | @KotlinxBenchmarkRuntimeInternalApi 75 | @Suppress("REDUNDANT_ELSE_IN_WHEN") 76 | fun BenchmarkTimeUnit.toSecondsMultiplier() = when (this) { 77 | BenchmarkTimeUnit.NANOSECONDS -> 1.0 / 1_000_000_000 78 | BenchmarkTimeUnit.MICROSECONDS -> 1.0 / 1_000_000 79 | BenchmarkTimeUnit.MILLISECONDS -> 1.0 / 1_000 80 | BenchmarkTimeUnit.SECONDS -> 1.0 81 | BenchmarkTimeUnit.MINUTES -> 60.0 82 | else -> throw UnsupportedOperationException("$this is not supported") 83 | } 84 | 85 | @Target(AnnotationTarget.CLASS) 86 | expect annotation class Warmup( 87 | val iterations: Int = -1, 88 | val time: Int = -1, 89 | val timeUnit: BenchmarkTimeUnit = BenchmarkTimeUnit.SECONDS, 90 | val batchSize: Int = -1 91 | ) 92 | 93 | @Target(AnnotationTarget.CLASS) 94 | expect annotation class Measurement( 95 | val iterations: Int = -1, 96 | val time: Int = -1, 97 | val timeUnit: BenchmarkTimeUnit = BenchmarkTimeUnit.SECONDS, 98 | val batchSize: Int = -1 99 | ) 100 | 101 | expect annotation class Param(vararg val value: String) -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/JsMultiplatformTasks.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi 4 | import org.gradle.api.* 5 | import org.jetbrains.kotlin.gradle.dsl.JsModuleKind 6 | import org.jetbrains.kotlin.gradle.targets.js.dsl.* 7 | import org.jetbrains.kotlin.gradle.targets.js.ir.* 8 | import org.jetbrains.kotlin.serialization.js.ModuleKind 9 | 10 | @KotlinxBenchmarkPluginInternalApi 11 | fun Project.processJsCompilation(target: JsBenchmarkTarget) { 12 | project.logger.info("Configuring benchmarks for '${target.name}' using Kotlin/JS") 13 | val compilation = target.compilation 14 | 15 | createJsBenchmarkGenerateSourceTask(target, compilation) 16 | 17 | val benchmarkCompilation = createJsBenchmarkCompileTask(target) 18 | 19 | target.extension.configurations.forEach { 20 | createJsEngineBenchmarkExecTask(it, target, benchmarkCompilation) 21 | } 22 | } 23 | 24 | private fun Project.createJsBenchmarkCompileTask(target: JsBenchmarkTarget): KotlinJsIrCompilation { 25 | val compilation = target.compilation 26 | val benchmarkBuildDir = benchmarkBuildDir(target) 27 | val benchmarkCompilation = 28 | compilation.target.compilations.create(target.name + BenchmarksPlugin.BENCHMARK_COMPILATION_SUFFIX) as KotlinJsIrCompilation 29 | 30 | (compilation.target as KotlinJsTargetDsl).apply { 31 | //force to create executable: required for IR, do nothing on Legacy 32 | binaries.executable(benchmarkCompilation) 33 | } 34 | 35 | benchmarkCompilation.apply { 36 | val sourceSet = kotlinSourceSets.single() 37 | 38 | sourceSet.kotlin.setSrcDirs(files("$benchmarkBuildDir/sources")) 39 | sourceSet.resources.setSrcDirs(files()) 40 | 41 | associateWith(compilation) 42 | 43 | sourceSet.dependencies { 44 | implementation(npm("benchmark", "*")) 45 | runtimeOnly(npm("source-map-support", "*")) 46 | } 47 | 48 | compileTaskProvider.configure { 49 | it.apply { 50 | group = BenchmarksPlugin.BENCHMARKS_TASK_GROUP 51 | description = "Compile JS benchmark source files for '${target.name}'" 52 | 53 | //TODO: fix destination dir after KT-29711 is fixed 54 | //println("JS: ${kotlinOptions.outputFile}") 55 | //destinationDir = file("$benchmarkBuildDir/classes") 56 | dependsOn("${target.name}${BenchmarksPlugin.BENCHMARK_GENERATE_SUFFIX}") 57 | 58 | compilerOptions { 59 | sourceMap.set(true) 60 | compilation.kotlinOptions.moduleKind?.let { 61 | moduleKind.set(JsModuleKind.fromKind(it)) 62 | } 63 | } 64 | } 65 | } 66 | } 67 | return benchmarkCompilation 68 | } 69 | 70 | private fun Project.createJsBenchmarkGenerateSourceTask( 71 | target: JsBenchmarkTarget, 72 | compilationOutput: KotlinJsIrCompilation 73 | ) { 74 | val benchmarkBuildDir = benchmarkBuildDir(target) 75 | task("${target.name}${BenchmarksPlugin.BENCHMARK_GENERATE_SUFFIX}") { 76 | group = BenchmarksPlugin.BENCHMARKS_TASK_GROUP 77 | description = "Generate JS source files for '${target.name}'" 78 | title = target.name 79 | useBenchmarkJs = target.jsBenchmarksExecutor == JsBenchmarksExecutor.BenchmarkJs 80 | inputClassesDirs = compilationOutput.output.classesDirs 81 | inputDependencies = compilationOutput.runtimeDependencyFiles 82 | outputResourcesDir = file("$benchmarkBuildDir/resources") 83 | outputSourcesDir = file("$benchmarkBuildDir/sources") 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/RunnerConfiguration.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | @KotlinxBenchmarkRuntimeInternalApi 6 | class RunnerConfiguration(config: String) { 7 | 8 | private val values = config.lines().groupBy({ 9 | it.substringBefore(":") 10 | }, { it.substringAfter(":", "") }) 11 | 12 | val name = singleValue("name") 13 | val reportFile = singleValue("reportFile") 14 | val traceFormat = singleValue("traceFormat") 15 | val reportFormat = singleValue("reportFormat", "json") 16 | 17 | val params = mapValues( 18 | "param", "=" 19 | ) 20 | 21 | val include = listValues("include") 22 | val exclude = listValues("exclude") 23 | 24 | val iterations = singleValueOrNull("iterations") { it.toInt() } 25 | val warmups = singleValueOrNull("warmups") { it.toInt() } 26 | val iterationTime = singleValueOrNull("iterationTime") { it.toLong() } 27 | val iterationTimeUnit = singleValueOrNull("iterationTimeUnit") { parseTimeUnit(it) } 28 | val advanced = mapSingleValues("advanced", "=") 29 | 30 | val outputTimeUnit = singleValueOrNull( 31 | "outputTimeUnit" 32 | ) { parseTimeUnit(it) } 33 | 34 | private fun singleValueOrNull(name: String, map: (String) -> T): T? = 35 | singleValueOrNull(name)?.let(map) 36 | 37 | private fun singleValueOrNull(name: String): String? { 38 | val values = values[name] ?: return null 39 | return values.single() 40 | } 41 | 42 | private fun singleValue(name: String): String { 43 | return singleValueOrNull(name) ?: throw NoSuchElementException("Parameter `$name` is required.") 44 | } 45 | 46 | private fun singleValue(name: String, default: String): String { 47 | return singleValueOrNull(name) ?: default 48 | } 49 | 50 | private fun mapValues(name: String, delimiter: String): Map> { 51 | val values = values[name] ?: return emptyMap() 52 | return values.groupBy({ it.substringBefore(delimiter) }, { it.substringAfter(delimiter) }) 53 | } 54 | 55 | private fun mapSingleValues(name: String, delimiter: String): Map = values[name] 56 | ?.associate { 57 | val splitted = it.split(delimiter) 58 | check(splitted.size == 2) { "Parameter name and value format is required for $name." } 59 | splitted[0] to splitted[1] 60 | } ?: emptyMap() 61 | 62 | private fun listValues(name: String): List { 63 | return this.values[name] ?: emptyList() 64 | } 65 | 66 | val mode = singleValueOrNull( 67 | "mode" 68 | ) { it.toMode() } 69 | 70 | override fun toString(): String { 71 | return """$name -> $reportFile ($traceFormat, $reportFormat) 72 | params: ${params.entries.joinToString(prefix = "{", postfix = "}") { "${it.key}: ${it.value}" }} 73 | include: $include 74 | exclude: $exclude 75 | iterations: $iterations 76 | warmups: $warmups 77 | iterationTime: $iterationTime 78 | iterationTimeUnit: $iterationTimeUnit 79 | outputTimeUnit: $outputTimeUnit 80 | mode: $mode 81 | advanced: $advanced 82 | """ 83 | } 84 | } 85 | 86 | internal fun parseTimeUnit(text: String) = when (text) { 87 | BenchmarkTimeUnit.SECONDS.name, "s", "sec" -> BenchmarkTimeUnit.SECONDS 88 | BenchmarkTimeUnit.MICROSECONDS.name, "us", "micros" -> BenchmarkTimeUnit.MICROSECONDS 89 | BenchmarkTimeUnit.MILLISECONDS.name, "ms", "millis" -> BenchmarkTimeUnit.MILLISECONDS 90 | BenchmarkTimeUnit.NANOSECONDS.name, "ns", "nanos" -> BenchmarkTimeUnit.NANOSECONDS 91 | BenchmarkTimeUnit.MINUTES.name, "m", "min" -> BenchmarkTimeUnit.MINUTES 92 | else -> throw UnsupportedOperationException("Unknown time unit: $text") 93 | } -------------------------------------------------------------------------------- /runtime/nativeMain/src/kotlinx/benchmark/NativeBlackhole.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | import kotlinx.cinterop.toByte 5 | import kotlin.concurrent.Volatile 6 | import kotlin.native.identityHashCode 7 | import kotlin.random.Random 8 | 9 | @OptIn(ExperimentalStdlibApi::class) 10 | @Suppress("NOTHING_TO_INLINE") 11 | actual class Blackhole { 12 | @KotlinxBenchmarkRuntimeInternalApi 13 | @Volatile 14 | var i0: Int = Random.nextInt() 15 | @KotlinxBenchmarkRuntimeInternalApi 16 | var i1 = i0 + 1 17 | 18 | @KotlinxBenchmarkRuntimeInternalApi 19 | @Volatile 20 | var l0 = Random.nextLong() 21 | @KotlinxBenchmarkRuntimeInternalApi 22 | var l1 = l0 + 1L 23 | 24 | @KotlinxBenchmarkRuntimeInternalApi 25 | @Volatile 26 | var f0 = Random.nextFloat() 27 | @KotlinxBenchmarkRuntimeInternalApi 28 | var f1 = f0 + 1.0f 29 | 30 | @Volatile 31 | @KotlinxBenchmarkRuntimeInternalApi 32 | var d0 = Random.nextDouble() 33 | @KotlinxBenchmarkRuntimeInternalApi 34 | var d1 = d0 + 1.0 35 | 36 | @KotlinxBenchmarkRuntimeInternalApi 37 | @Volatile 38 | var bh: Blackhole? = null 39 | 40 | actual inline fun consume(obj: Any?) { 41 | // identityHashCode is an intrinsic function 42 | // resolved into getting an object address, so there will be no call. 43 | consume(obj.identityHashCode()) 44 | } 45 | 46 | actual inline fun consume(bool: Boolean) { 47 | consume(bool.toByte()) 48 | } 49 | 50 | actual inline fun consume(c: Char) { 51 | consume(c.code) 52 | } 53 | 54 | actual inline fun consume(b: Byte) { 55 | consume(b.toInt()) 56 | } 57 | 58 | actual inline fun consume(s: Short) { 59 | consume(s.toInt()) 60 | } 61 | 62 | actual inline fun consume(i: Int) { 63 | // To ensure that i's value will not be removed by optimizations like dead code elimination, 64 | // its value is compares with two value i0 and i1, such that i1 = i0 + 1. 65 | // As long as i0 and i1 are different, the following condition should not ever be met 66 | // and as the branch following it (note that if it is executed, then NPE will happen). 67 | // To ensure that at least i0 value will be loaded on every call, it was annotated with Volatile. 68 | // 69 | // This approach has one drawback: in general, it should be compiled to a code with two branch instructions, 70 | // and performance characteristics of a benchmark may not be stable if consumed value is sometimes equal to i0. 71 | // In practice, there is almost no effect on the measured performance and 72 | // the difference is within the error margin. 73 | // However, if it becomes a problem one day, then the condition should be rewritten to something like: 74 | // if (((i0 xor i) and (i1 xor i)) == -1) { ... } 75 | // We can't simply compare xor results as it could be optimized to comparison of i0 and i1 and i's evaluation 76 | // may be sunk into the unreachable branch. 77 | if ((i0 == i) && (i1 == i)) { 78 | bh!!.i0 = i 79 | } 80 | } 81 | 82 | actual inline fun consume(l: Long) { 83 | if ((l0 == l) && (l1 == l)) { 84 | bh!!.l0 = l 85 | } 86 | } 87 | 88 | actual inline fun consume(f: Float) { 89 | if ((f0 == f) && (f1 == f)) { 90 | bh!!.f0 = f 91 | } 92 | } 93 | 94 | actual inline fun consume(d: Double) { 95 | if ((d0 == d) && (d1 == d)) { 96 | bh!!.d0 = d 97 | } 98 | } 99 | } 100 | 101 | @KotlinxBenchmarkRuntimeInternalApi 102 | actual fun Blackhole.flush() = Unit 103 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/SuiteExecutor.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | import kotlinx.benchmark.internal.KotlinxBenchmarkRuntimeInternalApi 4 | 5 | @KotlinxBenchmarkRuntimeInternalApi 6 | abstract class SuiteExecutor( 7 | val executionName: String, 8 | configPath: String, 9 | xmlReporter: (() -> BenchmarkProgress)? = null 10 | ) { 11 | private val config = RunnerConfiguration(configPath.readFile()) 12 | 13 | val reporter = BenchmarkProgress.create(config.traceFormat, xmlReporter) 14 | 15 | private val reportFormatter = BenchmarkReportFormatter.create(config.reportFormat) 16 | 17 | private val results = mutableListOf() 18 | 19 | private val suites = mutableListOf>() 20 | 21 | fun suite(descriptor: SuiteDescriptor) { 22 | suites.add(descriptor) 23 | } 24 | 25 | fun run() { 26 | //println(config.toString()) 27 | val include = if (config.include.isEmpty()) 28 | listOf(Regex(".*")) 29 | else 30 | config.include.map { Regex(it) } 31 | val exclude = config.exclude.map { Regex(it) } 32 | 33 | @Suppress("UNCHECKED_CAST") 34 | val benchmarks = suites.flatMap { suite -> 35 | suite.benchmarks 36 | .filter { benchmark -> 37 | val fullName = suite.name + "." + benchmark.name 38 | include.any { it.containsMatchIn(fullName) } && exclude.none { it.containsMatchIn(fullName) } 39 | } as List> 40 | } 41 | 42 | run(config, benchmarks, { reporter.startSuite(executionName) }) { 43 | val summary = TextBenchmarkReportFormatter.format(results) 44 | reporter.endSuite(executionName, summary) 45 | config.reportFile.writeFile(reportFormatter.format(results)) 46 | } 47 | } 48 | 49 | fun result(result: ReportBenchmarkResult) { 50 | results.add(result) 51 | } 52 | 53 | abstract fun run( 54 | runnerConfiguration: RunnerConfiguration, 55 | benchmarks: List>, 56 | start: () -> Unit, 57 | complete: () -> Unit 58 | ) 59 | 60 | protected fun id(name: String, params: Map): String { 61 | val id = if (params.isEmpty()) 62 | name 63 | else 64 | name + params.entries.joinToString(prefix = " | ") { "${it.key}=${it.value}" } 65 | return id 66 | } 67 | } 68 | 69 | @KotlinxBenchmarkRuntimeInternalApi 70 | fun runWithParameters( 71 | names: List, 72 | parameters: Map>, 73 | defaults: Map>, 74 | function: (Map) -> Unit 75 | ) { 76 | if (names.isEmpty()) { 77 | function(mapOf()) 78 | return 79 | } 80 | 81 | fun parameterValues(name: String): List { 82 | return parameters.getOrElse(name) { 83 | defaults.getOrElse(name) { 84 | error("No value specified for parameter '$name'") 85 | } 86 | } 87 | } 88 | 89 | val valueIndices = IntArray(names.size) 90 | val valueLimits = IntArray(names.size) { 91 | val name = names[it] 92 | parameterValues(name).size 93 | } 94 | while (true) { 95 | val paramsVariant = names.indices.associateBy({ names[it] }, { 96 | parameterValues(names[it])[valueIndices[it]] 97 | }) 98 | function(paramsVariant) 99 | for (index in valueIndices.indices) { 100 | valueIndices[index]++ 101 | if (valueIndices[index] < valueLimits[index]) 102 | break 103 | else 104 | if (index == valueIndices.lastIndex) 105 | return 106 | valueIndices[index] = 0 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /integration/src/main/kotlin/kotlinx/benchmark/integration/Runner.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | import kotlinx.benchmark.integration.GradleTestVersion.MinSupportedGradleVersion 4 | import org.gradle.testkit.runner.BuildResult 5 | import org.gradle.testkit.runner.GradleRunner 6 | import java.io.File 7 | 8 | class Runner( 9 | private val projectDir: File, 10 | private val print: Boolean, 11 | gradleVersion: GradleTestVersion? = null, 12 | ) { 13 | /** Defaults to the minimum Gradle version specified in [kotlinx.benchmark.gradle.BenchmarksPlugin] */ 14 | private val gradleVersion: GradleTestVersion = gradleVersion ?: MinSupportedGradleVersion 15 | 16 | private fun gradle(vararg tasks: String): GradleRunner = 17 | GradleRunner.create() 18 | .withProjectDir(projectDir) 19 | .withArguments(*(defaultArguments() + kotlinNativeVersion + tasks)) 20 | .withGradleVersion(gradleVersion.versionString) 21 | .forwardStdError(System.err.bufferedWriter()) 22 | .run { 23 | if (print) forwardStdOutput(System.out.bufferedWriter()) else this 24 | } 25 | 26 | fun run(vararg tasks: String, fn: BuildResult.() -> Unit) { 27 | val gradle = gradle(*tasks) 28 | @Suppress("UnstableApiUsage") 29 | val buildResult = gradle.run() 30 | buildResult.fn() 31 | } 32 | 33 | fun runAndSucceed(vararg tasks: String, fn: BuildResult.() -> Unit = {}) { 34 | val gradle = gradle(*tasks) 35 | val buildResult = gradle.build() 36 | buildResult.fn() 37 | } 38 | 39 | fun runAndFail(vararg tasks: String, fn: BuildResult.() -> Unit = {}) { 40 | val gradle = gradle(*tasks) 41 | val buildResult = gradle.buildAndFail() 42 | buildResult.fn() 43 | } 44 | 45 | private fun defaultArguments(): Array = arrayOf("--stacktrace") 46 | 47 | // Forward the Kotlin Native distribution version to test projects 48 | private val kotlinNativeVersion = "kotlin.native.version".let { property -> 49 | System.getProperty(property)?.let { arrayOf("-P$property=$it") } ?: emptyArray() 50 | } 51 | 52 | fun updateAnnotations(filePath: String, annotationsSpecifier: AnnotationsSpecifier.() -> Unit) { 53 | val annotations = AnnotationsSpecifier().also(annotationsSpecifier) 54 | val file = projectDir.resolve(filePath) 55 | 56 | val updatedLines = file.readLines().map { 57 | annotations.replaceClassAnnotation(it) 58 | } 59 | annotations.checkAllAnnotationsAreUsed() 60 | 61 | file.writeText(updatedLines.joinToString(separator = "\n")) 62 | if (print) { 63 | println(file.readText()) 64 | } 65 | } 66 | 67 | fun addAnnotation(filePath: String, annotationsSpecifier: AnnotationsSpecifier.() -> Unit) { 68 | val annotations = AnnotationsSpecifier().also(annotationsSpecifier) 69 | val file = projectDir.resolve(filePath) 70 | 71 | val updatedLines = mutableListOf() 72 | 73 | file.readLines().forEach { line -> 74 | val indentation = " ".repeat(line.length - line.trimStart().length) 75 | annotations.annotationsForFunction(line).forEach { annotation -> 76 | updatedLines.add(indentation + annotation) 77 | } 78 | annotations.annotationsForProperty(line).forEach { annotation -> 79 | updatedLines.add(indentation + annotation) 80 | } 81 | updatedLines.add(line) 82 | } 83 | annotations.checkAllAnnotationsAreUsed() 84 | 85 | file.writeText(updatedLines.joinToString(separator = "\n")) 86 | if (print) { 87 | println(file.readText()) 88 | } 89 | } 90 | 91 | fun generatedDir(targetName: String, filePath: String, fileTestAction: (File) -> Unit) { 92 | fileTestAction( 93 | projectDir.resolve("build/benchmarks/${targetName}/sources/kotlinx/benchmark/generated").resolve(filePath) 94 | ) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /integration/src/main/kotlin/kotlinx/benchmark/integration/ProjectBuilder.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | class ProjectBuilder { 4 | private val configurations = mutableMapOf() 5 | 6 | var kotlinVersion: String = System.getProperty("kotlin_version") 7 | var jvmToolchain: Int = 8 8 | 9 | fun configuration(name: String, configuration: BenchmarkConfiguration.() -> Unit = {}) { 10 | configurations[name] = BenchmarkConfiguration().apply(configuration) 11 | } 12 | 13 | fun build(original: String): String { 14 | 15 | val script = 16 | """ 17 | benchmark { 18 | configurations { 19 | ${configurations.flatMap { it.value.lines(it.key) }.joinToString("\n ")} 20 | } 21 | } 22 | """.trimIndent() 23 | 24 | return generateBuildScript(kotlinVersion, jvmToolchain) + "\n\n" + original + "\n\n" + script 25 | } 26 | } 27 | 28 | private val kotlin_repo = System.getProperty("kotlin_repo_url")?.let { 29 | "maven { url '${it.replace("\\","\\\\")}' }" 30 | }.orEmpty() 31 | 32 | private val plugin_repo_url = System.getProperty("plugin_repo_url")!!.let { 33 | "maven { url '${it.replace("\\","\\\\")}' }" 34 | } 35 | 36 | private val runtime_repo_url = System.getProperty("runtime_repo_url")!!.let { 37 | "maven { url '${it.replace("\\","\\\\")}' }" 38 | } 39 | 40 | private val kotlin_language_version = System.getProperty("kotlin_language_version")?.let { 41 | "languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.fromVersion('$it')" 42 | }.orEmpty() 43 | 44 | private val kotlin_api_version = System.getProperty("kotlin_api_version")?.let { 45 | "apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.fromVersion('$it')" 46 | }.orEmpty() 47 | 48 | private val kotlin_warnings_settings = System.getProperty("kotlin_Werror_override").let { 49 | when (it) { 50 | "disable" -> "" 51 | else -> "allWarningsAsErrors = true" 52 | } 53 | } 54 | 55 | private val kotlin_additional_cli_options = System.getProperty("kotlin_additional_cli_options")?.let { 56 | val argsList = it.split(' ').map(String::trim).filter(String::isNotBlank) 57 | if (argsList.isEmpty()) { 58 | "" 59 | } else { 60 | argsList.joinToString(prefix = "\"", separator = "\", \"", postfix = "\"") { opt -> 61 | opt.replace("\\", "\\\\") 62 | .replace("\n", "\\n") 63 | .replace("\t", "\\t") 64 | .replace("\b", "\\b") 65 | .replace("\r", "\\r") 66 | .replace("\"", "\\\"") 67 | } 68 | } 69 | } ?: "" 70 | 71 | private fun generateBuildScript(kotlinVersion: String, jvmToolchain: Int) = 72 | """ 73 | buildscript { 74 | repositories { 75 | $kotlin_repo 76 | $plugin_repo_url 77 | mavenCentral() 78 | } 79 | dependencies { 80 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion' 81 | classpath 'org.jetbrains.kotlinx:kotlinx-benchmark-plugin:0.5.0-SNAPSHOT' 82 | } 83 | } 84 | 85 | apply plugin: 'kotlin-multiplatform' 86 | apply plugin: 'org.jetbrains.kotlinx.benchmark' 87 | 88 | repositories { 89 | $kotlin_repo 90 | $runtime_repo_url 91 | mavenCentral() 92 | } 93 | 94 | kotlin { 95 | jvmToolchain($jvmToolchain) 96 | 97 | sourceSets { 98 | commonMain { 99 | dependencies { 100 | implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.5.0-SNAPSHOT") 101 | } 102 | } 103 | } 104 | } 105 | 106 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask).configureEach { 107 | compilerOptions { 108 | $kotlin_language_version 109 | $kotlin_api_version 110 | 111 | progressiveMode = true 112 | $kotlin_warnings_settings 113 | $kotlin_additional_cli_options 114 | } 115 | } 116 | """.trimIndent() 117 | -------------------------------------------------------------------------------- /runtime/commonMain/src/kotlinx/benchmark/IntelliJLog.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark 2 | 3 | internal fun ijSuiteStart(parent: String, id: String) = buildString { 4 | append("") 5 | append("") 6 | append("") 7 | append("") 8 | append("") 9 | append("") 10 | append("") 11 | } 12 | 13 | internal fun ijSuiteFinish( 14 | parent: String, id: String, status: BenchmarkProgress.FinishStatus, 15 | startTime: Long = 0, endTime: Long = startTime 16 | ) = buildString { 17 | append("") 18 | append("") 19 | append("") 20 | append("") 21 | append("") 22 | append("") 23 | append("") 24 | } 25 | 26 | internal fun ijBenchmarkStart(parent: String, className: String, methodName: String) = buildString { 27 | append("") 28 | append("") 29 | append("") 30 | append("") 31 | append("") 32 | append("") 33 | append("") 34 | } 35 | 36 | internal fun ijBenchmarkFinish( 37 | parent: String, id: String, status: BenchmarkProgress.FinishStatus, 38 | startTime: Long = 0, endTime: Long = startTime 39 | ) = buildString { 40 | append("") 41 | append("") 42 | append("") 43 | append("") 44 | append("") 45 | append("") 46 | append("") 47 | } 48 | 49 | internal fun ijBenchmarkFinishException( 50 | parent: String, id: String, error: String, stacktrace: String, 51 | startTime: Long = 0, endTime: Long = startTime 52 | ) = buildString { 53 | append("") 54 | append("") 55 | append("") 56 | append("") 57 | append("") 58 | append("") 59 | append("") 60 | append("") 61 | append("") 62 | append("") 63 | append("") 64 | append("") 65 | append("") 66 | append("") 67 | } 68 | 69 | internal fun ijLogOutput(parent: String, id: String, info: String) = buildString { 70 | append("") 71 | append("") 72 | append("") 73 | append("") 74 | append("") 75 | append("") 76 | append("") 77 | append("") 78 | append("") 79 | } 80 | 81 | private val BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 82 | private val BASE64_MASK: Byte = 0x3f 83 | private val BASE64_PAD = '=' 84 | 85 | private fun Int.toBase64(): Char = BASE64_ALPHABET[this] 86 | 87 | private fun ByteArray.encodeBase64(): String { 88 | fun ByteArray.getOrZero(index: Int): Int = if (index >= size) 0 else get(index).toInt() and 0xFF 89 | 90 | val result = ArrayList(4 * size / 3) 91 | var index = 0 92 | while (index < size) { 93 | val symbolsLeft = size - index 94 | val padSize = if (symbolsLeft >= 3) 0 else (3 - symbolsLeft) * 8 / 6 95 | val chunk = (getOrZero(index) shl 16) or (getOrZero(index + 1) shl 8) or getOrZero(index + 2) 96 | index += 3 97 | 98 | for (i in 3 downTo padSize) { 99 | val char = (chunk shr (6 * i)) and BASE64_MASK.toInt() 100 | result.add(char.toBase64()) 101 | } 102 | 103 | repeat(padSize) { result.add(BASE64_PAD) } 104 | } 105 | 106 | return result.toCharArray().concatToString() 107 | } 108 | -------------------------------------------------------------------------------- /.teamcity/utils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2020 JetBrains s.r.o. 3 | * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. 4 | */ 5 | 6 | import jetbrains.buildServer.configs.kotlin.* 7 | 8 | const val versionSuffixParameter = "versionSuffix" 9 | const val teamcitySuffixParameter = "teamcitySuffix" 10 | const val releaseVersionParameter = "releaseVersion" 11 | 12 | const val libraryStagingRepoDescription = "kotlinx-benchmark" 13 | 14 | val platforms = Platform.values() 15 | const val jdk = "JDK_17_0" 16 | 17 | enum class Platform { 18 | Windows, Linux, MacOS; 19 | } 20 | 21 | fun Platform.buildTypeName(): String = when (this) { 22 | Platform.Windows, Platform.Linux -> name 23 | Platform.MacOS -> "Mac OS X" 24 | } 25 | fun Platform.buildTypeId(): String = buildTypeName().substringBefore(" ") 26 | fun Platform.teamcityAgentName(): String = buildTypeName() 27 | 28 | 29 | const val BUILD_CONFIGURE_VERSION_ID = "Build_Version" 30 | const val BUILD_ALL_ID = "Build_All" 31 | const val DEPLOY_CONFIGURE_VERSION_ID = "Deploy_Configure" 32 | const val DEPLOY_PUBLISH_ID = "Deploy_Publish" 33 | 34 | val BUILD_CREATE_STAGING_REPO_ABSOLUTE_ID = AbsoluteId("KotlinTools_CreateSonatypeStagingRepository") 35 | 36 | class KnownBuilds(private val project: Project) { 37 | private fun buildWithId(id: String): BuildType { 38 | return project.buildTypes.single { it.id.toString().endsWith(id) } 39 | } 40 | 41 | val buildVersion: BuildType get() = buildWithId(BUILD_CONFIGURE_VERSION_ID) 42 | val buildAll: BuildType get() = buildWithId(BUILD_ALL_ID) 43 | fun buildOn(platform: Platform): BuildType = buildWithId("Build_${platform.buildTypeId()}") 44 | val deployVersion: BuildType get() = buildWithId(DEPLOY_CONFIGURE_VERSION_ID) 45 | val deployPublish: BuildType get() = buildWithId(DEPLOY_PUBLISH_ID) 46 | fun deployOn(platform: Platform): BuildType = buildWithId("Deploy_${platform.buildTypeId()}") 47 | } 48 | 49 | val Project.knownBuilds: KnownBuilds get() = KnownBuilds(this) 50 | 51 | 52 | fun Project.buildType(name: String, platform: Platform, configure: BuildType.() -> Unit) = BuildType { 53 | // ID is prepended with Project ID, so don't repeat it here 54 | // ID should conform to identifier rules, so just letters, numbers and underscore 55 | id("${name}_${platform.buildTypeId()}") 56 | // Display name of the build configuration 57 | this.name = "$name (${platform.buildTypeName()})" 58 | 59 | requirements { 60 | contains("teamcity.agent.jvm.os.name", platform.teamcityAgentName()) 61 | } 62 | 63 | params { 64 | // This parameter is needed for macOS agent to be compatible 65 | if (platform == Platform.MacOS) param("env.JDK_17", "") 66 | } 67 | 68 | commonConfigure() 69 | configure() 70 | }.also { buildType(it) } 71 | 72 | 73 | fun BuildType.commonConfigure() { 74 | requirements { 75 | noLessThan("teamcity.agent.hardware.memorySizeMb", "6144") 76 | } 77 | 78 | // Allow to fetch build status through API for badges 79 | allowExternalStatus = true 80 | 81 | // Configure VCS, by default use the same and only VCS root from which this configuration is fetched 82 | vcs { 83 | root(DslContext.settingsRoot) 84 | showDependenciesChanges = true 85 | checkoutMode = CheckoutMode.ON_AGENT 86 | } 87 | 88 | failureConditions { 89 | errorMessage = true 90 | nonZeroExitCode = true 91 | executionTimeoutMin = 120 92 | } 93 | 94 | features { 95 | feature { 96 | id = "perfmon" 97 | type = "perfmon" 98 | } 99 | } 100 | } 101 | 102 | fun BuildType.dependsOn(build: IdOwner, configure: Dependency.() -> Unit) = 103 | apply { 104 | dependencies.dependency(build, configure) 105 | } 106 | 107 | fun BuildType.dependsOnSnapshot(build: IdOwner, onFailure: FailureAction = FailureAction.FAIL_TO_START, configure: SnapshotDependency.() -> Unit = {}) = apply { 108 | dependencies.dependency(build) { 109 | snapshot { 110 | configure() 111 | onDependencyFailure = onFailure 112 | onDependencyCancel = FailureAction.CANCEL 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /plugin/main/src/kotlinx/benchmark/gradle/AnnotationsValidator.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.gradle 2 | 3 | import kotlinx.benchmark.gradle.SuiteSourceGenerator.Companion.paramAnnotationFQN 4 | import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable 5 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 6 | import org.jetbrains.kotlin.builtins.UnsignedTypes 7 | import org.jetbrains.kotlin.descriptors.DescriptorVisibilities 8 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 9 | import org.jetbrains.kotlin.descriptors.PropertyDescriptor 10 | import org.jetbrains.kotlin.js.descriptorUtils.getKotlinTypeFqName 11 | import org.jetbrains.kotlin.name.FqName 12 | import org.jetbrains.kotlin.resolve.annotations.argumentValue 13 | 14 | @RequiresKotlinCompilerEmbeddable 15 | internal fun validateBenchmarkFunctions(functions: List) { 16 | functions.forEach { function -> 17 | if (function.visibility != DescriptorVisibilities.PUBLIC) { 18 | error("@Benchmark function should be public. Function `${function.name}` is ${function.visibility.name}.") 19 | } 20 | 21 | val parameters = function.valueParameters.size 22 | if (parameters == 1) { 23 | val paramType = function.valueParameters[0].type 24 | if (paramType.getKotlinTypeFqName(false) != "kotlinx.benchmark.Blackhole") { 25 | error("@Benchmark function can have at most one parameter of type `Blackhole`. " + 26 | "Function `${function.name}` has a parameter of type `$paramType`. ") 27 | } 28 | } else if (parameters != 0) { 29 | error("@Benchmark function can have at most one parameter of type `Blackhole`. " + 30 | "Function `${function.name}` has $parameters parameters.") 31 | } 32 | } 33 | } 34 | 35 | @RequiresKotlinCompilerEmbeddable 36 | internal fun validateSetupFunctions(functions: List) { 37 | functions.forEach { function -> 38 | if (function.visibility != DescriptorVisibilities.PUBLIC) { 39 | error("@Setup function should be public. Function `${function.name}` is ${function.visibility.name}.") 40 | } 41 | 42 | val parameters = function.valueParameters.size 43 | if (parameters != 0) { 44 | error("@Setup function should have no parameters. " + 45 | "Function `${function.name}` has $parameters parameter${if (parameters > 1) "s" else ""}.") 46 | } 47 | } 48 | } 49 | 50 | @RequiresKotlinCompilerEmbeddable 51 | internal fun validateTeardownFunctions(functions: List) { 52 | functions.forEach { function -> 53 | if (function.visibility != DescriptorVisibilities.PUBLIC) { 54 | error("@TearDown function should be public. Function `${function.name}` is ${function.visibility.name}.") 55 | } 56 | 57 | val parameters = function.valueParameters.size 58 | if (parameters != 0) { 59 | error("@TearDown function should have no parameters. " + 60 | "Function `${function.name}` has $parameters parameter${if (parameters > 1) "s" else ""}.") 61 | } 62 | } 63 | } 64 | 65 | @RequiresKotlinCompilerEmbeddable 66 | internal fun validateParameterProperties(properties: List) { 67 | properties.forEach { property -> 68 | if (!property.isVar) { 69 | error("@Param property should be mutable (var). Property `${property.name}` is read-only (val).") 70 | } 71 | if (property.visibility != DescriptorVisibilities.PUBLIC) { 72 | error("@Param property should be public. Property `${property.name}` is ${property.visibility.name}.") 73 | } 74 | val isSupportedType = KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(property.type) || 75 | UnsignedTypes.isUnsignedType(property.type) || 76 | property.type.getKotlinTypeFqName(false) == "kotlin.String" 77 | if (!isSupportedType) { 78 | error("@Param property should have a primitive or string type. Property `${property.name}` type is `${property.type}`.") 79 | } 80 | 81 | val annotation = property.annotations.findAnnotation(FqName(paramAnnotationFQN))!! 82 | val valueArgument = annotation.argumentValue("value")!! 83 | val values = valueArgument.value as List<*> 84 | 85 | if (values.isEmpty()) { 86 | error("@Param annotation should have at least one argument. The annotation on property `${property.name}` has no arguments.") 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/kotlin-multiplatform/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalWasmDsl::class) 2 | 3 | import kotlinx.benchmark.gradle.* 4 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 5 | 6 | 7 | plugins { 8 | kotlin("multiplatform") 9 | kotlin("plugin.allopen") version "2.0.20" 10 | id("org.jetbrains.kotlinx.benchmark") 11 | } 12 | 13 | allOpen { 14 | annotation("org.openjdk.jmh.annotations.State") 15 | } 16 | 17 | kotlin { 18 | jvm { 19 | compilations.create("benchmark") { associateWith(this@jvm.compilations.getByName("main")) } 20 | } 21 | js { 22 | nodejs() 23 | val mainCompilation = compilations.getByName("main") 24 | compilations.create("defaultExecutor") { associateWith(mainCompilation) } 25 | compilations.create("builtInExecutor") { associateWith(mainCompilation) } 26 | } 27 | wasmJs { nodejs() } 28 | 29 | // Native targets 30 | macosX64() 31 | macosArm64() 32 | linuxX64() 33 | mingwX64() 34 | 35 | applyDefaultHierarchyTemplate() 36 | 37 | sourceSets { 38 | commonMain { 39 | dependencies { 40 | implementation(project(":kotlinx-benchmark-runtime")) 41 | } 42 | } 43 | 44 | jvmMain {} 45 | 46 | wasmJsMain {} 47 | 48 | val jsMain by getting 49 | 50 | getByName("jsDefaultExecutor") { 51 | dependsOn(jsMain) 52 | } 53 | 54 | getByName("jsBuiltInExecutor") { 55 | dependsOn(jsMain) 56 | } 57 | 58 | nativeMain {} 59 | } 60 | } 61 | 62 | // Configure benchmark 63 | benchmark { 64 | configurations { 65 | named("main") { // --> jvmBenchmark, jsBenchmark, Benchmark, benchmark 66 | iterations = 5 // number of iterations 67 | iterationTime = 300 68 | iterationTimeUnit = "ms" 69 | advanced("jvmForks", 3) 70 | advanced("jsUseBridge", true) 71 | } 72 | 73 | create("params") { 74 | iterations = 5 // number of iterations 75 | iterationTime = 300 76 | iterationTimeUnit = "ms" 77 | include("ParamBenchmark") 78 | param("data", 5, 1, 8) 79 | param("unused", 6, 9) 80 | } 81 | 82 | create("fast") { // --> jvmFastBenchmark, jsFastBenchmark, FastBenchmark, fastBenchmark 83 | include("Common") 84 | exclude("long") 85 | iterations = 5 86 | iterationTime = 300 // time in ms per iteration 87 | iterationTimeUnit = "ms" // time in ms per iteration 88 | advanced("nativeGCAfterIteration", true) 89 | } 90 | 91 | create("csv") { 92 | include("Common") 93 | exclude("long") 94 | iterations = 1 95 | iterationTime = 300 96 | iterationTimeUnit = "ms" 97 | reportFormat = "csv" // csv report format 98 | } 99 | 100 | create("fork") { 101 | include("CommonBenchmark") 102 | iterations = 5 103 | iterationTime = 300 104 | iterationTimeUnit = "ms" 105 | advanced("jvmForks", "definedByJmh") // see README.md for possible "jvmForks" values 106 | advanced("nativeFork", "perIteration") // see README.md for possible "nativeFork" values 107 | } 108 | } 109 | 110 | // Setup configurations 111 | targets { 112 | // This one matches the target name, e.g. 'jvm', 'js', 113 | // and registers its 'main' compilation, so 'jvm' registers 'jvmMain' 114 | register("jvm") { 115 | this as JvmBenchmarkTarget 116 | jmhVersion = "1.37" 117 | } 118 | // This one matches the source set name, e.g. 'jvmMain', 'jvmTest', etc 119 | // and register the corresponding compilation (here the 'benchmark' compilation declared in the 'jvm' target) 120 | register("jvmBenchmark") { 121 | this as JvmBenchmarkTarget 122 | jmhVersion = "1.37" 123 | } 124 | register("jsDefaultExecutor") 125 | register("jsBuiltInExecutor") { 126 | this as JsBenchmarkTarget 127 | jsBenchmarksExecutor = JsBenchmarksExecutor.BuiltIn 128 | } 129 | register("wasmJs") 130 | 131 | // Native targets 132 | register("macosX64") 133 | register("macosArm64") 134 | register("linuxX64") 135 | register("mingwX64") 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /integration/src/main/kotlin/kotlinx/benchmark/integration/AnnotationsSpecifier.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.benchmark.integration 2 | 3 | @OptIn(ExperimentalStdlibApi::class) 4 | class AnnotationsSpecifier { 5 | private val classAnnotations = mutableListOf() 6 | private val propertyAnnotations = mutableListOf() 7 | private val functionAnnotations = mutableListOf() 8 | 9 | fun measurement(iterations: Int, time: Int, timeUnit: String) { 10 | classAnnotations.add( 11 | Annotation("@Measurement", listOf(iterations, time, timeUnit)) 12 | ) 13 | } 14 | 15 | fun warmup(iterations: Int, time: Int, timeUnit: String) { 16 | classAnnotations.add( 17 | Annotation("@Warmup", listOf(iterations, time, timeUnit)) 18 | ) 19 | } 20 | 21 | fun outputTimeUnit(timeUnit: String) { 22 | classAnnotations.add( 23 | Annotation("@OutputTimeUnit", listOf(timeUnit)) 24 | ) 25 | } 26 | 27 | fun benchmarkMode(mode: String) { 28 | classAnnotations.add( 29 | Annotation("@BenchmarkMode", listOf(mode)) 30 | ) 31 | } 32 | 33 | fun benchmark(functionName: String) { 34 | functionAnnotations.add( 35 | AnnotatedMember(functionName, Annotation("@Benchmark")) 36 | ) 37 | } 38 | 39 | fun setup(functionName: String) { 40 | functionAnnotations.add( 41 | AnnotatedMember(functionName, Annotation("@Setup")) 42 | ) 43 | } 44 | 45 | fun teardown(functionName: String) { 46 | functionAnnotations.add( 47 | AnnotatedMember(functionName, Annotation("@TearDown")) 48 | ) 49 | } 50 | 51 | fun param(propertyName: String, vararg values: String) { 52 | require(values.all { '\"' !in it }) { "TODO: Support param values that contain '\"'." } 53 | 54 | propertyAnnotations.add( 55 | AnnotatedMember(propertyName, Annotation("@Param", values.map { "\"$it\"" })) 56 | ) 57 | } 58 | 59 | fun annotationsForProperty(line: String): List { 60 | val annotations = mutableListOf() 61 | for ((propertyName, annotation) in propertyAnnotations) { 62 | val regex = Regex("\\s*(public|private|protected|internal)?\\s*(final|open)?\\s*(val|var)\\s+${Regex.escape(propertyName)}") 63 | if (regex.matchesAt(line, 0)) { 64 | check(!annotation.isUsed) 65 | annotation.isUsed = true 66 | annotations.add(annotation.toCode()) 67 | } 68 | } 69 | return annotations 70 | } 71 | 72 | fun annotationsForFunction(line: String): List { 73 | val annotations = mutableListOf() 74 | for ((functionName, annotation) in functionAnnotations) { 75 | val regex = Regex("\\s*(public|private|protected|internal)?\\s*(final|open)?\\s*fun\\s+${Regex.escape(functionName)}\\(") 76 | if (regex.matchesAt(line, 0)) { 77 | check(!annotation.isUsed) 78 | annotation.isUsed = true 79 | annotations.add(annotation.toCode()) 80 | } 81 | } 82 | return annotations 83 | } 84 | 85 | fun replaceClassAnnotation(line: String): String { 86 | val trimmedLine = line.trimStart() 87 | val prefix = line.substring(0, line.length - trimmedLine.length) 88 | for (annotation in classAnnotations) { 89 | if (trimmedLine.startsWith(annotation.name)) { 90 | check(!annotation.isUsed) 91 | annotation.isUsed = true 92 | return prefix + annotation.toCode() 93 | } 94 | } 95 | return line 96 | } 97 | 98 | fun checkAllAnnotationsAreUsed() { 99 | classAnnotations.forEach { check(it.isUsed) { "Unused class annotation: $it" } } 100 | propertyAnnotations.forEach { check(it.annotation.isUsed) { "Unused property annotation: $it" } } 101 | functionAnnotations.forEach { check(it.annotation.isUsed) { "Unused function annotation: $it" } } 102 | } 103 | } 104 | 105 | private data class AnnotatedMember( 106 | val memberName: String, 107 | val annotation: Annotation 108 | ) 109 | 110 | private data class Annotation( 111 | val name: String, 112 | val arguments: List = emptyList(), 113 | var isUsed: Boolean = false 114 | ) { 115 | fun toCode(): String = 116 | "$name${if (arguments.isEmpty()) "" else arguments.joinToString(", ", "(", ")")}" 117 | } --------------------------------------------------------------------------------