From f8d72cc200610bf3a9b58ebfd0aeefb852fb5617 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 21 Nov 2023 12:46:33 +0100 Subject: [PATCH 1/2] Introduce jvmBenchmarks into kotlinx-coroutines-core It allows us to benchmark `internal` API unavailable otherwise and allows us to backport K2 changes properly. Only benchmarks that somehow leverage `internal` API are moved in the new source-set --- kotlinx-coroutines-core/build.gradle | 11 +++++++++++ kotlinx-coroutines-core/jvmBenchmark/README.md | 15 +++++++++++++++ .../kotlin/kotlinx/coroutines/BenchmarkUtils.kt | 17 +++++++++++++++++ .../kotlinx/coroutines}/SemaphoreBenchmark.kt | 6 ++---- .../ChannelProducerConsumerBenchmark.kt | 9 ++++----- .../coroutines/channels}/SelectBenchmark.kt | 4 ++-- .../coroutines/channels}/SimpleChannel.kt | 7 ++----- .../channels}/SimpleChannelBenchmark.kt | 4 ++-- .../coroutines}/flow/TakeWhileBenchmark.kt | 11 +++++------ 9 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 kotlinx-coroutines-core/jvmBenchmark/README.md create mode 100644 kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/BenchmarkUtils.kt rename {benchmarks/src/jmh/kotlin/benchmarks => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines}/SemaphoreBenchmark.kt (94%) rename {benchmarks/src/jmh/kotlin/benchmarks => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels}/ChannelProducerConsumerBenchmark.kt (94%) rename {benchmarks/src/jmh/kotlin/benchmarks/tailcall => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels}/SelectBenchmark.kt (90%) rename {benchmarks/src/jmh/kotlin/benchmarks/tailcall => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels}/SimpleChannel.kt (92%) rename {benchmarks/src/jmh/kotlin/benchmarks/tailcall => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels}/SimpleChannelBenchmark.kt (92%) rename {benchmarks/src/jmh/kotlin/benchmarks => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines}/flow/TakeWhileBenchmark.kt (89%) diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 86350ce546..2c7f8883ae 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -2,6 +2,10 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +plugins { + id 'org.jetbrains.kotlinx.benchmark' version '0.4.9' +} + apply plugin: 'org.jetbrains.kotlin.multiplatform' apply plugin: 'org.jetbrains.dokka' @@ -167,6 +171,13 @@ kotlin { // For animal sniffer withJava() + compilations.create('benchmark') { associateWith(compilations.main) } + } +} + +benchmark { + targets { + register("jvmBenchmark") } } diff --git a/kotlinx-coroutines-core/jvmBenchmark/README.md b/kotlinx-coroutines-core/jvmBenchmark/README.md new file mode 100644 index 0000000000..78eeb6c01d --- /dev/null +++ b/kotlinx-coroutines-core/jvmBenchmark/README.md @@ -0,0 +1,15 @@ +## kotlinx-coroutines-core benchmarks + +This source-set contains benchmarks that leverage `internal` API (e.g. `suspendCancellableCoroutineReusable`) +and thus cannot be written in `benchmarks` module. + +This is an interim solution unless we introduce clear separation of responsibilities in benchmark modules +and decide on their usability. + + +### Usage + +``` +./gradlew :kotlinx-coroutines-core:jvmBenchmarkBenchmarkJar +java -jar kotlinx-coroutines-core/build/benchmarks/jvmBenchmark/jars/kotlinx-coroutines-core-jvmBenchmark-jmh-1.7.2-SNAPSHOT-JMH.jar +``` diff --git a/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/BenchmarkUtils.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/BenchmarkUtils.kt new file mode 100644 index 0000000000..714aad114c --- /dev/null +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/BenchmarkUtils.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import java.util.concurrent.* + +public fun doGeomDistrWork(work: Int) { + // We use geometric distribution here. We also checked on macbook pro 13" (2017) that the resulting work times + // are distributed geometrically, see https://github.com/Kotlin/kotlinx.coroutines/pull/1464#discussion_r355705325 + val p = 1.0 / work + val r = ThreadLocalRandom.current() + while (true) { + if (r.nextDouble() < p) break + } +} diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt similarity index 94% rename from benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt index 6826b7a1a3..4620d2ef89 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt @@ -1,10 +1,9 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks +package kotlinx.coroutines -import benchmarks.common.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.scheduling.* @@ -81,7 +80,6 @@ open class SemaphoreBenchmark { enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) { // FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) }) } diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/ChannelProducerConsumerBenchmark.kt similarity index 94% rename from benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/ChannelProducerConsumerBenchmark.kt index 0aa218e824..7bc0d2b2be 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/ChannelProducerConsumerBenchmark.kt @@ -1,16 +1,16 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks +package kotlinx.coroutines.channels -import benchmarks.common.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.scheduling.* import kotlinx.coroutines.selects.select import org.openjdk.jmh.annotations.* import java.lang.Integer.max +import java.util.concurrent.ForkJoinPool import java.util.concurrent.Phaser import java.util.concurrent.TimeUnit @@ -136,8 +136,7 @@ open class ChannelProducerConsumerBenchmark { } enum class DispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) { - //FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) }) } diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SelectBenchmark.kt similarity index 90% rename from benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SelectBenchmark.kt index cb4d39eed6..e2bc9e4abf 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SelectBenchmark.kt @@ -1,8 +1,8 @@ /* - * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks.tailcall +package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlinx.coroutines.channels.* diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannel.kt similarity index 92% rename from benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannel.kt index 1f71d8dc19..1afb0f0464 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannel.kt @@ -1,14 +1,13 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks.tailcall +package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* -@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") public abstract class SimpleChannel { companion object { const val NULL_SURROGATE: Int = -1 @@ -83,13 +82,11 @@ class CancellableChannel : SimpleChannel() { } class CancellableReusableChannel : SimpleChannel() { - @Suppress("INVISIBLE_MEMBER") override suspend fun suspendReceive(): Int = suspendCancellableCoroutineReusable { consumer = it.intercepted() COROUTINE_SUSPENDED } - @Suppress("INVISIBLE_MEMBER") override suspend fun suspendSend(element: Int) = suspendCancellableCoroutineReusable { enqueuedValue = element producer = it.intercepted() diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannelBenchmark.kt similarity index 92% rename from benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannelBenchmark.kt index 9654b6dabe..233cecff4e 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannelBenchmark.kt @@ -1,8 +1,8 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks.tailcall +package kotlinx.coroutines.channels import kotlinx.coroutines.* import org.openjdk.jmh.annotations.* diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/flow/TakeWhileBenchmark.kt similarity index 89% rename from benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/flow/TakeWhileBenchmark.kt index 7501e2c419..6dad29e51f 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/flow/TakeWhileBenchmark.kt @@ -1,16 +1,15 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") - -package benchmarks.flow +package kotlinx.coroutines.flow import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.internal.* +import kotlinx.coroutines.flow.internal.AbortFlowException +import kotlinx.coroutines.flow.internal.unsafeFlow import org.openjdk.jmh.annotations.* -import java.util.concurrent.TimeUnit +import java.util.concurrent.* @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) From 8a28dc7b628ac4fef8e7f579cac161ea52cfd9ff Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 21 Nov 2023 13:41:39 +0100 Subject: [PATCH 2/2] ~ --- .../kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt index 4620d2ef89..df82690771 100644 --- a/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt @@ -79,7 +79,7 @@ open class SemaphoreBenchmark { } enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) { - // FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), + FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) }) }