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

3.1.0-beta05 #115

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions .idea/runConfigurations/All_benchmarks.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.kotlinMultiplatform.id)
id(libs.plugins.kotlin.multiplatform.id)
id(libs.plugins.androidLibrary.id)
alias(libs.plugins.maven.publish)
dokkaDocumentation
Expand Down
66 changes: 66 additions & 0 deletions benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import configureMultiplatform
import kotlinx.benchmark.gradle.JvmBenchmarkTarget
import kotlinx.benchmark.gradle.benchmark

@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id(libs.plugins.kotlin.multiplatform.id)
alias(libs.plugins.kotlin.benchmark)
alias(libs.plugins.kotlin.allopen)
}

allOpen { // jmh benchmark classes must be open
annotation("org.openjdk.jmh.annotations.State")
}
kotlin {
configureMultiplatform(
ext = this,
explicitApi = false,
wasmWasi = false,
android = false,
linux = false,
iOs = false,
macOs = false,
watchOs = false,
tvOs = false,
windows = false,
wasmJs = false,
)

}
tasks.withType<JavaExec>().configureEach {
jvmArgs("-Dkotlinx.coroutines.debug=off")
}
dependencies {
commonMainImplementation(projects.core)

val fluxo = "0.1-2306082-SNAPSHOT"
//noinspection UseTomlInstead
commonMainImplementation("io.github.fluxo-kt:fluxo-core:$fluxo")

commonMainImplementation(libs.kotlin.coroutines.test)
commonMainImplementation(libs.kotlin.test)
commonMainImplementation(libs.kotlin.benchmark)
}

benchmark {
configurations {
named("main") {
iterations = 100
warmups = 10
iterationTime = 100
iterationTimeUnit = "ms"
outputTimeUnit = "ms"
mode = "thrpt" // "thrpt" - throughput, "avgt" - average
reportFormat = "text"
// advanced("nativeGCAfterIteration", true)
// advanced("jvmForks", "definedByJmh")
}
}
targets {
register("jvm") {
this as JvmBenchmarkTarget
jmhVersion = libs.versions.jmh.get()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package pro.respawn.flowmvi.benchmarks

internal object BenchmarkDefaults {

const val intentsPerIteration = 1000
Nek-12 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pro.respawn.flowmvi.benchmarks.setup

import pro.respawn.flowmvi.api.MVIIntent

internal sealed interface BenchmarkIntent : MVIIntent {
data object Increment : BenchmarkIntent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pro.respawn.flowmvi.benchmarks.setup

import pro.respawn.flowmvi.api.MVIState

data class BenchmarkState(
val counter: Int = 0
) : MVIState
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package pro.respawn.flowmvi.benchmarks.setup.channelbased

import kotlinx.benchmark.TearDown
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.Setup
import org.openjdk.jmh.annotations.State
import pro.respawn.flowmvi.benchmarks.BenchmarkDefaults
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkIntent

@Suppress("unused")
@State(Scope.Benchmark)
internal class ChannelTraditionalMVIBenchmark {

lateinit var store: ChannelBasedTraditionalStore
lateinit var scope: CoroutineScope

@Setup
fun setup() {
scope = CoroutineScope(Dispatchers.Unconfined)
store = ChannelBasedTraditionalStore(scope)
}

@Benchmark
fun benchmark() = runBlocking {
repeat(BenchmarkDefaults.intentsPerIteration) {
store.onIntent(BenchmarkIntent.Increment)
}
store.state.first { state -> state.counter >= BenchmarkDefaults.intentsPerIteration }
}

@TearDown
fun teardown() = runBlocking {
scope.cancel()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package pro.respawn.flowmvi.benchmarks.setup.channelbased

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkIntent
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkState

internal class ChannelBasedTraditionalStore(scope: CoroutineScope) {

private val _state = MutableStateFlow(BenchmarkState())
val state = _state.asStateFlow()
val intents = Channel<BenchmarkIntent>()

init {
scope.launch {
for (intent in intents) reduce(intent)
}
}

fun onIntent(intent: BenchmarkIntent) = intents.trySend(intent)

private fun reduce(intent: BenchmarkIntent) = when (intent) {
is BenchmarkIntent.Increment -> _state.update { state ->
state.copy(counter = state.counter + 1)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package pro.respawn.flowmvi.benchmarks.setup.fluxo

import kotlinx.benchmark.TearDown
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kt.fluxo.core.closeAndWait
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.Setup
import org.openjdk.jmh.annotations.State
import org.openjdk.jmh.annotations.Threads
import pro.respawn.flowmvi.benchmarks.BenchmarkDefaults
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkIntent
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkState

@Threads(Threads.MAX)
@Suppress("unused")
@State(Scope.Benchmark)
internal class FluxoIntentBenchmark {

lateinit var store: kt.fluxo.core.Store<BenchmarkIntent, BenchmarkState>

@Setup
fun setup() = runBlocking {
store = fluxoStore()
}

@Benchmark
fun benchmark() = runBlocking {
repeat(BenchmarkDefaults.intentsPerIteration) {
store.send(BenchmarkIntent.Increment)
}
store.first { state -> state.counter >= BenchmarkDefaults.intentsPerIteration }
}

@TearDown
fun teardown() = runBlocking {
store.closeAndWait()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package pro.respawn.flowmvi.benchmarks.setup.fluxo

import kotlinx.benchmark.Benchmark
import kotlinx.coroutines.runBlocking
import kt.fluxo.core.annotation.ExperimentalFluxoApi
import kt.fluxo.core.closeAndWait
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State
import org.openjdk.jmh.annotations.Threads

@Threads(Threads.MAX)
@State(Scope.Benchmark)
class FluxoStartStopBenchmark {

@OptIn(ExperimentalFluxoApi::class)
@Benchmark
fun benchmark() = runBlocking {
val store = fluxoStore()
store.start().join()
store.closeAndWait()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pro.respawn.flowmvi.benchmarks.setup.fluxo

import kotlinx.coroutines.Dispatchers
import kt.fluxo.core.annotation.ExperimentalFluxoApi
import kt.fluxo.core.store
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkIntent
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkState

@OptIn(ExperimentalFluxoApi::class)
internal inline fun fluxoStore(
) = store(BenchmarkState(), reducer = { it: BenchmarkIntent ->
when (it) {
BenchmarkIntent.Increment -> copy(counter = counter + 1)
}
}) {
coroutineContext = Dispatchers.Unconfined
intentStrategy = Direct
debugChecks = false
lazy = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pro.respawn.flowmvi.benchmarks.setup.optimized

import kotlinx.benchmark.TearDown
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.openjdk.jmh.annotations.Benchmark
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.Setup
import org.openjdk.jmh.annotations.State
import org.openjdk.jmh.annotations.Threads
import pro.respawn.flowmvi.api.Store
import pro.respawn.flowmvi.benchmarks.BenchmarkDefaults
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkIntent
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkState
import pro.respawn.flowmvi.dsl.collect

@Threads(Threads.MAX)
@Suppress("unused")
@State(Scope.Benchmark)
internal class OptimizedFMVIBenchmark {

lateinit var store: Store<BenchmarkState, BenchmarkIntent, Nothing>
lateinit var scope: CoroutineScope

@Setup
fun setup() = runBlocking {
scope = CoroutineScope(Dispatchers.Unconfined)
store = optimizedStore(scope)
store.awaitStartup()
}

@Benchmark
fun benchmark() = runBlocking {
repeat(BenchmarkDefaults.intentsPerIteration) {
store.intent(BenchmarkIntent.Increment)
}
store.collect { states.first { it.counter >= BenchmarkDefaults.intentsPerIteration } }
}

@TearDown
fun teardown() = runBlocking {
scope.cancel()
store.closeAndWait()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package pro.respawn.flowmvi.benchmarks.setup.optimized

import kotlinx.benchmark.Benchmark
import kotlinx.coroutines.runBlocking
import org.openjdk.jmh.annotations.Scope
import org.openjdk.jmh.annotations.State
import org.openjdk.jmh.annotations.Threads

@Threads(Threads.MAX)
@State(Scope.Benchmark)
internal class OptimizedFMVIStartStopBenchmark {

@Benchmark
fun benchmark() = runBlocking {
val store = optimizedStore()
store.start(this).awaitStartup()
store.closeAndWait()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package pro.respawn.flowmvi.benchmarks.setup.optimized

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import pro.respawn.flowmvi.api.ActionShareBehavior.Disabled
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkIntent
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkIntent.Increment
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkState
import pro.respawn.flowmvi.dsl.StoreBuilder
import pro.respawn.flowmvi.dsl.store
import pro.respawn.flowmvi.plugins.reduce
import pro.respawn.flowmvi.plugins.reducePlugin

internal fun StoreBuilder<*, *, *>.config() {
configure {
logger = null
debuggable = false
actionShareBehavior = Disabled
atomicStateUpdates = false
parallelIntents = false
verifyPlugins = false
onOverflow = BufferOverflow.DROP_OLDEST
intentCapacity = Channel.RENDEZVOUS
}
}

private val reduce = reducePlugin<BenchmarkState, BenchmarkIntent, Nothing> {
when (it) {
is Increment -> updateStateImmediate {
copy(counter = counter + 1)
}
}
}

internal inline fun optimizedStore(
scope: CoroutineScope,
) = store<BenchmarkState, BenchmarkIntent, Nothing>(BenchmarkState(), scope) {
config()
install(reduce)
}

internal inline fun optimizedStore() = store<BenchmarkState, BenchmarkIntent, Nothing>(BenchmarkState()) {
config()
install(reduce)
}
Loading
Loading