Skip to content

Commit d3f1f23

Browse files
authored
Multiplatform benchmarks baseline (#4350)
Update kotlinx-benchmark-based setup in kotlinx-coroutines-core and add a basic zero-contention benchmark for locks
1 parent 3ed623a commit d3f1f23

File tree

13 files changed

+103
-28
lines changed

13 files changed

+103
-28
lines changed

buildSrc/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ dependencies {
6363
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
6464
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
6565
}
66-
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-plugin:0.4.9")
66+
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-plugin:${version("benchmarks")}")
6767
implementation("org.jetbrains.kotlinx:kotlinx-knit:${version("knit")}")
6868
implementation("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${version("atomicfu")}")
6969
}

gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ group=org.jetbrains.kotlinx
44
kotlin_version=2.1.0
55
# DO NOT rename this property without adapting kotlinx.train build chain:
66
atomicfu_version=0.26.1
7+
benchmarks_version=0.4.13
8+
benchmarks_jmh_version=1.37
79

810
# Dependencies
911
junit_version=4.12
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## kotlinx-coroutines-core benchmarks
2+
3+
Multiplatform benchmarks for kotlinx-coroutines-core.
4+
5+
This source-set contains benchmarks that leverage `internal` API (e.g. `suspendCancellableCoroutineReusable`) or
6+
that are multiplatform (-> only supported with `kotlinx-benchmarks` which is less convenient than `jmh` plugin).
7+
For JVM-only non-internal benchmarks, consider using `benchmarks` top-level project.
8+
9+
### Usage
10+
11+
```
12+
// JVM only
13+
./gradlew :kotlinx-coroutines-core:jvmBenchmarkBenchmarkJar
14+
java -jar kotlinx-coroutines-core/build/benchmarks/jvmBenchmark/jars/kotlinx-coroutines-core-jvmBenchmark-jmh-*-JMH.jar
15+
16+
// Native, OS X
17+
./gradlew :kotlinx-coroutines-core:macosArm64BenchmarkBenchmark
18+
19+
// Figure out what to use
20+
./gradlew :kotlinx-coroutines-core:tasks | grep -i bench
21+
```

kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt renamed to kotlinx-coroutines-core/benchmarks/jvm/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ open class SemaphoreBenchmark {
7676

7777
enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) {
7878
FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }),
79-
DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) })
79+
DEFAULT({ parallelism -> CoroutineScheduler(corePoolSize = parallelism, maxPoolSize = parallelism).asCoroutineDispatcher() })
8080
}
8181

8282
private const val WORK_INSIDE = 50
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ open class ChannelProducerConsumerBenchmark {
133133

134134
enum class DispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) {
135135
FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }),
136-
DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) })
136+
DEFAULT({ parallelism -> CoroutineScheduler(corePoolSize = parallelism, maxPoolSize = parallelism).asCoroutineDispatcher() })
137137
}
138138

139139
enum class ChannelCreator(private val capacity: Int) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package kotlinx.coroutines
2+
3+
import kotlinx.coroutines.*
4+
import kotlinx.coroutines.flow.*
5+
import kotlinx.benchmark.*
6+
7+
// Stresses out 'syncrhonozed' codepath in MutableSharedFlow
8+
@State(Scope.Benchmark)
9+
@Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS)
10+
@OutputTimeUnit(BenchmarkTimeUnit.MICROSECONDS)
11+
@BenchmarkMode(Mode.AverageTime)
12+
open class SharedFlowBaseline {
13+
private var size: Int = 10_000
14+
15+
@Benchmark
16+
fun baseline() = runBlocking {
17+
val flow = MutableSharedFlow<Unit>()
18+
launch {
19+
repeat(size) { flow.emit(Unit) }
20+
}
21+
22+
flow.take(size).collect { }
23+
}
24+
}

kotlinx-coroutines-core/build.gradle.kts

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import org.gradle.api.tasks.testing.*
22
import org.gradle.kotlin.dsl.*
3+
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
4+
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
35
import org.jetbrains.kotlin.gradle.plugin.mpp.*
46
import org.jetbrains.kotlin.gradle.targets.native.tasks.*
57
import org.jetbrains.kotlin.gradle.tasks.*
@@ -59,6 +61,8 @@ kotlin {
5961
}
6062
}
6163
}
64+
setupBenchmarkSourceSets(sourceSets)
65+
6266
/*
6367
* Configure two test runs for Native:
6468
* 1) Main thread
@@ -84,13 +88,45 @@ kotlin {
8488
jvm {
8589
// For animal sniffer
8690
withJava()
87-
compilations.create("benchmark") { associateWith(this@jvm.compilations.getByName("main")) }
8891
}
8992
}
9093

91-
benchmark {
92-
targets {
93-
register("jvmBenchmark")
94+
private fun KotlinMultiplatformExtension.setupBenchmarkSourceSets(ss: NamedDomainObjectContainer<KotlinSourceSet>) {
95+
// Forgive me, Father, for I have sinned.
96+
// Really, that is needed to have benchmark sourcesets be the part of the project, not a separate project
97+
val benchmarkMain by ss.creating {
98+
dependencies {
99+
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:${version("benchmarks")}")
100+
}
101+
// For each source set we have to manually set path to the sources, otherwise lookup will fail
102+
kotlin.srcDir("benchmarks/main/kotlin")
103+
}
104+
105+
@Suppress("UnusedVariable")
106+
val jvmBenchmark by ss.creating {
107+
// For each source set we have to manually set path to the sources, otherwise lookup will fail
108+
kotlin.srcDir("benchmarks/jvm/kotlin")
109+
}
110+
111+
targets.matching {
112+
it.name != "metadata"
113+
// Doesn't work, don't want to figure it out for now
114+
&& !it.name.contains("wasm")
115+
&& !it.name.contains("js")
116+
}.all {
117+
compilations.create("benchmark") {
118+
associateWith(this@all.compilations.getByName("main"))
119+
defaultSourceSet {
120+
dependencies {
121+
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:${version("benchmarks")}")
122+
}
123+
dependsOn(benchmarkMain)
124+
}
125+
}
126+
}
127+
128+
targets.matching { it.name != "metadata" }.all {
129+
benchmark.targets.register("${name}Benchmark")
94130
}
95131
}
96132

@@ -131,10 +167,12 @@ val allMetadataJar by tasks.getting(Jar::class) { setupManifest(this) }
131167

132168
fun setupManifest(jar: Jar) {
133169
jar.manifest {
134-
attributes(mapOf(
135-
"Premain-Class" to "kotlinx.coroutines.debug.internal.AgentPremain",
136-
"Can-Retransform-Classes" to "true",
137-
))
170+
attributes(
171+
mapOf(
172+
"Premain-Class" to "kotlinx.coroutines.debug.internal.AgentPremain",
173+
"Can-Retransform-Classes" to "true",
174+
)
175+
)
138176
}
139177
}
140178

@@ -187,9 +225,11 @@ fun Test.configureJvmForLincheck(segmentSize: Int = 1) {
187225
minHeapSize = "1g"
188226
maxHeapSize = "4g" // we may need more space for building an interleaving tree in the model checking mode
189227
// https://github.com/JetBrains/lincheck#java-9
190-
jvmArgs = listOf("--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", // required for transformation
228+
jvmArgs = listOf(
229+
"--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", // required for transformation
191230
"--add-exports", "java.base/sun.security.action=ALL-UNNAMED",
192-
"--add-exports", "java.base/jdk.internal.util=ALL-UNNAMED") // in the model checking mode
231+
"--add-exports", "java.base/jdk.internal.util=ALL-UNNAMED"
232+
) // in the model checking mode
193233
// Adjust internal algorithmic parameters to increase the testing quality instead of performance.
194234
systemProperty("kotlinx.coroutines.semaphore.segmentSize", segmentSize)
195235
systemProperty("kotlinx.coroutines.semaphore.maxSpinCycles", 1) // better for the model checking mode
@@ -215,6 +255,9 @@ kover {
215255
// lincheck has NPE error on `ManagedStrategyStateHolder` class
216256
excludedClasses.addAll("org.jetbrains.kotlinx.lincheck.*")
217257
}
258+
sources {
259+
excludedSourceSets.addAll("benchmark")
260+
}
218261
}
219262

220263
reports {

kotlinx-coroutines-core/jvmBenchmark/README.md

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)