Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiplatform benchmarks baseline #4350

Merged
merged 7 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ dependencies {
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
}
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-plugin:0.4.9")
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-plugin:${version("benchmarks")}")
implementation("org.jetbrains.kotlinx:kotlinx-knit:${version("knit")}")
implementation("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${version("atomicfu")}")
}
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ group=org.jetbrains.kotlinx
kotlin_version=2.1.0
# DO NOT rename this property without adapting kotlinx.train build chain:
atomicfu_version=0.26.1
benchmarks_version=0.4.13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth plugging benchmarks_jmh_version=1.37 here, otherwise benchmarks will use pretty ancient version of JMH (1.21).


# Dependencies
junit_version=4.12
Expand Down
21 changes: 21 additions & 0 deletions kotlinx-coroutines-core/benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## kotlinx-coroutines-core benchmarks

Multiplatform benchmarks for kotlinx-coroutines-core.

This source-set contains benchmarks that leverage `internal` API (e.g. `suspendCancellableCoroutineReusable`) or
that are multiplatform (-> only supported with `kotlinx-benchmarks` which is less convenient than `jmh` plugin).
For JVM-only non-internal benchmarks, consider using `benchmarks` top-level project.

### Usage

```
// JVM only
./gradlew :kotlinx-coroutines-core:jvmBenchmarkBenchmarkJar
java -jar kotlinx-coroutines-core/build/benchmarks/jvmBenchmark/jars/kotlinx-coroutines-core-jvmBenchmark-jmh-*-JMH.jar

// Native, OS X
./gradlew :kotlinx-coroutines-core:macosArm64BenchmarkBenchmark

// Figure out what to use
./gradlew :kotlinx-coroutines-core:tasks | grep -i bench
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
./gradlew :kotlinx-coroutines-core:tasks | grep -i bench
./gradlew :kotlinx-coroutines-core:tasks | grep -i "BenchmarkBenchmark "

Seems much more reliable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explicitly opted into mine version -- shorter, more robust (also, will showcase non-launcing tasks, for example macosArm64BenchmarkBinaries to copy around)

```
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ open class SemaphoreBenchmark {

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

private const val WORK_INSIDE = 50
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ open class ChannelProducerConsumerBenchmark {

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

enum class ChannelCreator(private val capacity: Int) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kotlinx.coroutines

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.benchmark.*

// Stresses out 'syncrhonozed' codepath in MutableSharedFlow
@State(Scope.Benchmark)
@Measurement(iterations = 3, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS)
@OutputTimeUnit(BenchmarkTimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
open class SharedFlowBaseline {
private var size: Int = 10_000

@Benchmark
fun baseline() = runBlocking {
val flow = MutableSharedFlow<Unit>()
launch {
repeat(size) { flow.emit(Unit) }
}

flow.take(size).collect { }
}
}
63 changes: 53 additions & 10 deletions kotlinx-coroutines-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import org.gradle.api.tasks.testing.*
import org.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.targets.native.tasks.*
import org.jetbrains.kotlin.gradle.tasks.*
Expand Down Expand Up @@ -59,6 +61,8 @@ kotlin {
}
}
}
setupBenchmarkSourceSets(sourceSets)

/*
* Configure two test runs for Native:
* 1) Main thread
Expand All @@ -84,13 +88,45 @@ kotlin {
jvm {
// For animal sniffer
withJava()
compilations.create("benchmark") { associateWith([email protected]("main")) }
}
}

benchmark {
targets {
register("jvmBenchmark")
private fun KotlinMultiplatformExtension.setupBenchmarkSourceSets(ss: NamedDomainObjectContainer<KotlinSourceSet>) {
// Forgive me, Father, for I have sinned.
// Really, that is needed to have benchmark sourcesets be the part of the project, not a separate project
val benchmarkMain by ss.creating {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:${version("benchmarks")}")
}
// For each source set we have to manually set path to the sources, otherwise lookup will fail
kotlin.srcDir("benchmarks/main/kotlin")
}

@Suppress("UnusedVariable")
val jvmBenchmark by ss.creating {
// For each source set we have to manually set path to the sources, otherwise lookup will fail
kotlin.srcDir("benchmarks/jvm/kotlin")
}

targets.matching {
it.name != "metadata"
// Doesn't work, don't want to figure it out for now
&& !it.name.contains("wasm")
&& !it.name.contains("js")
}.all {
compilations.create("benchmark") {
associateWith([email protected]("main"))
defaultSourceSet {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:${version("benchmarks")}")
}
dependsOn(benchmarkMain)
}
}
}

targets.matching { it.name != "metadata" }.all {
benchmark.targets.register("${name}Benchmark")
}
}

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

fun setupManifest(jar: Jar) {
jar.manifest {
attributes(mapOf(
"Premain-Class" to "kotlinx.coroutines.debug.internal.AgentPremain",
"Can-Retransform-Classes" to "true",
))
attributes(
mapOf(
"Premain-Class" to "kotlinx.coroutines.debug.internal.AgentPremain",
"Can-Retransform-Classes" to "true",
)
)
}
}

Expand Down Expand Up @@ -187,9 +225,11 @@ fun Test.configureJvmForLincheck(segmentSize: Int = 1) {
minHeapSize = "1g"
maxHeapSize = "4g" // we may need more space for building an interleaving tree in the model checking mode
// https://github.com/JetBrains/lincheck#java-9
jvmArgs = listOf("--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", // required for transformation
jvmArgs = listOf(
"--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", // required for transformation
"--add-exports", "java.base/sun.security.action=ALL-UNNAMED",
"--add-exports", "java.base/jdk.internal.util=ALL-UNNAMED") // in the model checking mode
"--add-exports", "java.base/jdk.internal.util=ALL-UNNAMED"
) // in the model checking mode
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pro tip: use Difftastic when reviewing such changes! In a structural diff, the trivial formatting edits don't show up at all.

// Adjust internal algorithmic parameters to increase the testing quality instead of performance.
systemProperty("kotlinx.coroutines.semaphore.segmentSize", segmentSize)
systemProperty("kotlinx.coroutines.semaphore.maxSpinCycles", 1) // better for the model checking mode
Expand All @@ -215,6 +255,9 @@ kover {
// lincheck has NPE error on `ManagedStrategyStateHolder` class
excludedClasses.addAll("org.jetbrains.kotlinx.lincheck.*")
}
sources {
excludedSourceSets.addAll("benchmark")
}
}

reports {
Expand Down
15 changes: 0 additions & 15 deletions kotlinx-coroutines-core/jvmBenchmark/README.md

This file was deleted.