From da15456609534297185203bb35893ce1d1664c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petrus=20Nguy=E1=BB=85n=20Th=C3=A1i=20H=E1=BB=8Dc?= Date: Fri, 1 Mar 2024 10:07:01 +0700 Subject: [PATCH 01/15] flatten: use loop and ArrayDequene instead of recursion --- .../kotlin/org/koin/core/module/Module.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt index c5b1f1687..a34105a6a 100644 --- a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt +++ b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt @@ -235,12 +235,19 @@ operator fun List.plus(module: Module): List = this + listOf(mod * Run through the module list to flatten all modules & submodules */ @OptIn(KoinInternalApi::class) -fun flatten(modules: List): Set { - fun flat(modules: List, newModules: MutableSet){ - modules.forEach{ - newModules += it - flat(it.includedModules,newModules) +fun flatten(modules: List): Set { + val flatten = HashSet() + val stack = ArrayDeque(modules) + + while (queue.isNotEmpty()) { + val current = stack.removeLast() + flatten.add(current) + current.includedModules.forEach { + if (it !in flatten) { + stack.addLast(it) + } } } - return mutableSetOf().apply { flat(modules,this) } + + return flatten } From e98514a1f3d17dd7e7560f0114e88a152e56de8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petrus=20Nguy=E1=BB=85n=20Th=C3=A1i=20H=E1=BB=8Dc?= Date: Fri, 1 Mar 2024 10:29:32 +0700 Subject: [PATCH 02/15] Update Module.kt --- .../src/commonMain/kotlin/org/koin/core/module/Module.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt index a34105a6a..233f17363 100644 --- a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt +++ b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt @@ -239,7 +239,7 @@ fun flatten(modules: List): Set { val flatten = HashSet() val stack = ArrayDeque(modules) - while (queue.isNotEmpty()) { + while (stack.isNotEmpty()) { val current = stack.removeLast() flatten.add(current) current.includedModules.forEach { From 704839897d1c8464a2e8842c98870b6832a56f24 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Sat, 2 Mar 2024 15:32:47 +0700 Subject: [PATCH 03/15] add flatten benchmark --- examples/jvm-perfs/benchmark.txt | 1 + examples/jvm-perfs/build.gradle.kts | 4 +- .../org/koin/benchmark/BenchmarkClass.kt | 6 ++ .../org/koin/benchmark/nestedModules.kt | 80 +++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt diff --git a/examples/jvm-perfs/benchmark.txt b/examples/jvm-perfs/benchmark.txt index 004ecdf31..da75ab7b2 100644 --- a/examples/jvm-perfs/benchmark.txt +++ b/examples/jvm-perfs/benchmark.txt @@ -2,3 +2,4 @@ Benchmark Mode Cnt Score Error Units BenchmarkClass.emptyStart avgt 10 ≈ 10⁻³ ms/op BenchmarkClass.start400 avgt 10 0,272 ± 0,045 ms/op BenchmarkClass.start400AndInject avgt 10 0,265 ± 0,022 ms/op +BenchmarkClass.flatten avgt 10 1.190 ± 0.024 ms/op diff --git a/examples/jvm-perfs/build.gradle.kts b/examples/jvm-perfs/build.gradle.kts index 8feae4e4e..4083d5479 100644 --- a/examples/jvm-perfs/build.gradle.kts +++ b/examples/jvm-perfs/build.gradle.kts @@ -5,7 +5,7 @@ plugins { kotlin("kapt") } -val jvmTarget = "11" +val jvmTarget = "17" tasks.getByName("compileJava") { targetCompatibility = jvmTarget @@ -17,7 +17,7 @@ tasks.getByName("compileKotlin" val jmhVersion = "1.36" //TODO get from existing version.gradle file -val koin_version = "3.5.2-RC1" +val koin_version = "3.5.3" val coroutines_version = "1.7.3" dependencies { diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index 34ed14461..b8bd2b834 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -1,6 +1,7 @@ package org.koin.benchmark import org.koin.benchmark.PerfRunner.koinScenario +import org.koin.core.context.startKoin import org.koin.dsl.koinApplication import org.openjdk.jmh.annotations.* import java.util.concurrent.TimeUnit @@ -33,4 +34,9 @@ open class BenchmarkClass { }.koin koinScenario(koin) } + + @Benchmark + fun flatten() { + org.koin.core.module.flatten(nestedModules) + } } \ No newline at end of file diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt new file mode 100644 index 000000000..fc9bc5f39 --- /dev/null +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt @@ -0,0 +1,80 @@ +package org.koin.benchmark + +import org.koin.core.module.Module +import org.koin.core.qualifier.qualifier +import org.koin.dsl.module + +private val level0Modules = List(100) { + module {} +} + +private val level1Modules = List(100) { + val q = qualifier("q-$it") + module { + includes(level0Modules) + single(q) { A1() } + single(q) { A2() } + single(q) { A3() } + single(q) { A4() } + single(q) { A5() } + } +} + +private val level2Modules = List(100) { + val q = qualifier("q-$it") + module { + includes(level0Modules) + includes(level1Modules[it]) + + single(q) { A6() } + single(q) { A7() } + single(q) { A8() } + single(q) { A9() } + single(q) { A10() } + } +} + +private val level3Modules = List(100) { + val q = qualifier("q-$it") + module { + includes(level0Modules) + includes(level2Modules[it]) + + single(q) { A11() } + single(q) { A12() } + single(q) { A13() } + single(q) { A14() } + single(q) { A15() } + } +} + +private val level4Modules = List(100) { + val q = qualifier("q-$it") + module { + includes(level0Modules) + includes(level3Modules[it]) + + single(q) { A16() } + single(q) { A17() } + single(q) { A18() } + single(q) { A19() } + single(q) { A20() } + } +} + +internal val nestedModules: List = List(100) { + val q = qualifier("q-$it") + module { + includes(level0Modules) + includes(level1Modules) + includes(level2Modules) + includes(level3Modules) + includes(level4Modules) + + single(q) { A16() } + single(q) { A17() } + single(q) { A18() } + single(q) { A19() } + single(q) { A20() } + } +} \ No newline at end of file From efe260fe56cd101ae25b51124e380220368f7045 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Sat, 2 Mar 2024 15:38:07 +0700 Subject: [PATCH 04/15] add flatten benchmark --- examples/jvm-perfs/benchmark.txt | 10 +++++----- .../main/kotlin/org/koin/benchmark/BenchmarkClass.kt | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/jvm-perfs/benchmark.txt b/examples/jvm-perfs/benchmark.txt index da75ab7b2..e4dc90ad8 100644 --- a/examples/jvm-perfs/benchmark.txt +++ b/examples/jvm-perfs/benchmark.txt @@ -1,5 +1,5 @@ -Benchmark Mode Cnt Score Error Units -BenchmarkClass.emptyStart avgt 10 ≈ 10⁻³ ms/op -BenchmarkClass.start400 avgt 10 0,272 ± 0,045 ms/op -BenchmarkClass.start400AndInject avgt 10 0,265 ± 0,022 ms/op -BenchmarkClass.flatten avgt 10 1.190 ± 0.024 ms/op +Benchmark Mode Cnt Score Error Units +BenchmarkClass.emptyStart avgt 10 ≈ 10⁻³ ms/op +BenchmarkClass.flatten avgt 10 138.186 ± 12.825 ms/op +BenchmarkClass.start400 avgt 10 0.511 ± 0.341 ms/op +BenchmarkClass.start400AndInject avgt 10 0.373 ± 0.100 ms/op \ No newline at end of file diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index b8bd2b834..8ba0ee401 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -1,7 +1,6 @@ package org.koin.benchmark import org.koin.benchmark.PerfRunner.koinScenario -import org.koin.core.context.startKoin import org.koin.dsl.koinApplication import org.openjdk.jmh.annotations.* import java.util.concurrent.TimeUnit From eaf0158c315ce5a4319e58097072fd582164cc05 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Sat, 2 Mar 2024 15:46:24 +0700 Subject: [PATCH 05/15] add flatten benchmark --- examples/jvm-perfs/benchmark.txt | 7 +- .../org/koin/benchmark/BenchmarkClass.kt | 5 + .../org/koin/benchmark/nestedModules.kt | 125 ++++++++++-------- 3 files changed, 81 insertions(+), 56 deletions(-) diff --git a/examples/jvm-perfs/benchmark.txt b/examples/jvm-perfs/benchmark.txt index e4dc90ad8..40b566670 100644 --- a/examples/jvm-perfs/benchmark.txt +++ b/examples/jvm-perfs/benchmark.txt @@ -1,5 +1,6 @@ Benchmark Mode Cnt Score Error Units BenchmarkClass.emptyStart avgt 10 ≈ 10⁻³ ms/op -BenchmarkClass.flatten avgt 10 138.186 ± 12.825 ms/op -BenchmarkClass.start400 avgt 10 0.511 ± 0.341 ms/op -BenchmarkClass.start400AndInject avgt 10 0.373 ± 0.100 ms/op \ No newline at end of file +BenchmarkClass.flatten avgt 10 146.838 ± 15.775 ms/op +BenchmarkClass.newFlatten avgt 10 0.880 ± 0.018 ms/op +BenchmarkClass.start400 avgt 10 0.282 ± 0.005 ms/op +BenchmarkClass.start400AndInject avgt 10 0.278 ± 0.004 ms/op \ No newline at end of file diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index 8ba0ee401..f74338476 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -38,4 +38,9 @@ open class BenchmarkClass { fun flatten() { org.koin.core.module.flatten(nestedModules) } + + @Benchmark + fun newFlatten() { + newFlatten(nestedModules) + } } \ No newline at end of file diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt index fc9bc5f39..b5860ee1d 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt @@ -1,80 +1,99 @@ package org.koin.benchmark +import org.koin.core.annotation.KoinInternalApi import org.koin.core.module.Module import org.koin.core.qualifier.qualifier import org.koin.dsl.module private val level0Modules = List(100) { - module {} + module {} } private val level1Modules = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - single(q) { A1() } - single(q) { A2() } - single(q) { A3() } - single(q) { A4() } - single(q) { A5() } - } + val q = qualifier("q-$it") + module { + includes(level0Modules) + single(q) { A1() } + single(q) { A2() } + single(q) { A3() } + single(q) { A4() } + single(q) { A5() } + } } private val level2Modules = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - includes(level1Modules[it]) + val q = qualifier("q-$it") + module { + includes(level0Modules) + includes(level1Modules[it]) - single(q) { A6() } - single(q) { A7() } - single(q) { A8() } - single(q) { A9() } - single(q) { A10() } - } + single(q) { A6() } + single(q) { A7() } + single(q) { A8() } + single(q) { A9() } + single(q) { A10() } + } } private val level3Modules = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - includes(level2Modules[it]) + val q = qualifier("q-$it") + module { + includes(level0Modules) + includes(level2Modules[it]) - single(q) { A11() } - single(q) { A12() } - single(q) { A13() } - single(q) { A14() } - single(q) { A15() } - } + single(q) { A11() } + single(q) { A12() } + single(q) { A13() } + single(q) { A14() } + single(q) { A15() } + } } private val level4Modules = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - includes(level3Modules[it]) + val q = qualifier("q-$it") + module { + includes(level0Modules) + includes(level3Modules[it]) - single(q) { A16() } - single(q) { A17() } - single(q) { A18() } - single(q) { A19() } - single(q) { A20() } - } + single(q) { A16() } + single(q) { A17() } + single(q) { A18() } + single(q) { A19() } + single(q) { A20() } + } } internal val nestedModules: List = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - includes(level1Modules) - includes(level2Modules) - includes(level3Modules) - includes(level4Modules) + val q = qualifier("q-$it") + module { + includes(level0Modules) + includes(level1Modules) + includes(level2Modules) + includes(level3Modules) + includes(level4Modules) - single(q) { A16() } - single(q) { A17() } - single(q) { A18() } - single(q) { A19() } - single(q) { A20() } - } + single(q) { A16() } + single(q) { A17() } + single(q) { A18() } + single(q) { A19() } + single(q) { A20() } + } +} + +@OptIn(KoinInternalApi::class) +internal fun newFlatten(modules: List): Set { + val flatten = HashSet() + val stack = ArrayDeque(modules) + + while (stack.isNotEmpty()) { + val current = stack.removeLast() + flatten.add(current) + current.includedModules.forEach { + if (it !in flatten) { + stack.addLast(it) + } + } + } + + return flatten } \ No newline at end of file From d3ba6771b6a2a00d39410e19b7dee35613acbf53 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Sat, 2 Mar 2024 15:51:44 +0700 Subject: [PATCH 06/15] jvm 11 --- examples/jvm-perfs/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jvm-perfs/build.gradle.kts b/examples/jvm-perfs/build.gradle.kts index 4083d5479..523bf215a 100644 --- a/examples/jvm-perfs/build.gradle.kts +++ b/examples/jvm-perfs/build.gradle.kts @@ -5,7 +5,7 @@ plugins { kotlin("kapt") } -val jvmTarget = "17" +val jvmTarget = "11" tasks.getByName("compileJava") { targetCompatibility = jvmTarget From 37d4a1903344f99bbc3da745fadfdd14efeb3b9c Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Tue, 5 Mar 2024 23:03:25 +0700 Subject: [PATCH 07/15] refactor flatten --- .../kotlin/org/koin/core/module/Module.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt index 233f17363..9530a5448 100644 --- a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt +++ b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt @@ -235,16 +235,25 @@ operator fun List.plus(module: Module): List = this + listOf(mod * Run through the module list to flatten all modules & submodules */ @OptIn(KoinInternalApi::class) -fun flatten(modules: List): Set { +fun flatten(modules: List): Set { + // This is essentially a DFS traversal of the module graph, + // but we're using a stack instead of recursion to avoid stack overflows and performance overhead. + val flatten = HashSet() - val stack = ArrayDeque(modules) + val stack = ArrayDeque(modules) while (stack.isNotEmpty()) { val current = stack.removeLast() - flatten.add(current) - current.includedModules.forEach { - if (it !in flatten) { - stack.addLast(it) + + // If the module is already in the set, that means we've already visited it, so we can skip it. + if (!flatten.add(current)) { + continue + } + + // Add all the included modules to the stack if they haven't been visited yet. + for (module in current.includedModules) { + if (module !in flatten) { + stack += module } } } From add4d7e83595351d01009447b692b06073a5b608 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Thu, 7 Mar 2024 15:34:19 +0700 Subject: [PATCH 08/15] update BenchmarkClass --- .../org/koin/benchmark/BenchmarkClass.kt | 19 +- .../org/koin/benchmark/nestedModules.kt | 164 ++++++++++-------- 2 files changed, 105 insertions(+), 78 deletions(-) diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index f74338476..35c303f3d 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -2,16 +2,25 @@ package org.koin.benchmark import org.koin.benchmark.PerfRunner.koinScenario import org.koin.dsl.koinApplication -import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.BenchmarkMode +import org.openjdk.jmh.annotations.Fork +import org.openjdk.jmh.annotations.Measurement +import org.openjdk.jmh.annotations.Mode +import org.openjdk.jmh.annotations.OutputTimeUnit +import org.openjdk.jmh.annotations.Warmup import java.util.concurrent.TimeUnit - @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Fork(value = 2, warmups = 0) @Measurement(iterations = 5, time = 2) @Warmup(iterations = 1) open class BenchmarkClass { + private val nestedModules = buildNestedModule( + depth = 128, + width = 256, + ) @Benchmark fun emptyStart() { @@ -35,12 +44,12 @@ open class BenchmarkClass { } @Benchmark - fun flatten() { + fun flattenRecursive() { org.koin.core.module.flatten(nestedModules) } @Benchmark - fun newFlatten() { - newFlatten(nestedModules) + fun flattenIterative() { + flattenIterative(nestedModules) } } \ No newline at end of file diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt index b5860ee1d..2941f1e27 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt @@ -5,92 +5,110 @@ import org.koin.core.module.Module import org.koin.core.qualifier.qualifier import org.koin.dsl.module -private val level0Modules = List(100) { - module {} -} - -private val level1Modules = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - single(q) { A1() } - single(q) { A2() } - single(q) { A3() } - single(q) { A4() } - single(q) { A5() } - } -} - -private val level2Modules = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - includes(level1Modules[it]) - - single(q) { A6() } - single(q) { A7() } - single(q) { A8() } - single(q) { A9() } - single(q) { A10() } +internal fun buildNestedModule( + depth: Int, + width: Int, +): List { + val matrix = List(depth) { r -> + List(width) { c -> + val q = qualifier("q_${r}_$c") + module { + single(q) { A1() } + single(q) { A2() } + single(q) { A3() } + single(q) { A4() } + single(q) { A5() } + single(q) { A6() } + single(q) { A7() } + single(q) { A8() } + single(q) { A9() } + single(q) { A10() } + single(q) { A11() } + single(q) { A12() } + single(q) { A13() } + single(q) { A14() } + single(q) { A15() } + single(q) { A16() } + single(q) { A17() } + single(q) { A18() } + single(q) { A19() } + single(q) { A20() } + single(q) { A21() } + single(q) { A22() } + single(q) { A23() } + single(q) { A24() } + single(q) { A25() } + single(q) { A26() } + single(q) { A27() } + single(q) { A28() } + single(q) { A29() } + single(q) { A30() } + single(q) { A31() } + single(q) { A32() } + single(q) { A33() } + single(q) { A34() } + single(q) { A35() } + single(q) { A36() } + single(q) { A37() } + single(q) { A38() } + single(q) { A39() } + single(q) { A40() } + single(q) { A41() } + single(q) { A42() } + single(q) { A43() } + single(q) { A44() } + single(q) { A45() } + single(q) { A46() } + single(q) { A47() } + single(q) { A48() } + single(q) { A49() } + single(q) { A50() } + } + } } -} -private val level3Modules = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - includes(level2Modules[it]) + // Random a row that should be included in all modules. + val level0Index = matrix.indices.random() + val level0 = matrix[level0Index] - single(q) { A11() } - single(q) { A12() } - single(q) { A13() } - single(q) { A14() } - single(q) { A15() } - } -} + matrix.forEachIndexed { r, modules -> + modules.forEachIndexed { c, module -> + // Let each module include the one above it. + matrix + .getOrNull(r + 1) + ?.get(c) + ?.let { module.includes(it) } -private val level4Modules = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - includes(level3Modules[it]) - - single(q) { A16() } - single(q) { A17() } - single(q) { A18() } - single(q) { A19() } - single(q) { A20() } + // Don't include itself. + if (r != level0Index) { + module.includes(level0) + } + } } -} - -internal val nestedModules: List = List(100) { - val q = qualifier("q-$it") - module { - includes(level0Modules) - includes(level1Modules) - includes(level2Modules) - includes(level3Modules) - includes(level4Modules) - single(q) { A16() } - single(q) { A17() } - single(q) { A18() } - single(q) { A19() } - single(q) { A20() } - } + return matrix.last() } @OptIn(KoinInternalApi::class) -internal fun newFlatten(modules: List): Set { +internal fun flattenIterative(modules: List): Set { + // This is essentially a DFS traversal of the module graph, + // but we're using a stack instead of recursion to avoid stack overflows and performance overhead. + val flatten = HashSet() - val stack = ArrayDeque(modules) + val stack = ArrayDeque(modules) while (stack.isNotEmpty()) { val current = stack.removeLast() - flatten.add(current) - current.includedModules.forEach { - if (it !in flatten) { - stack.addLast(it) + + // If the module is already in the set, that means we've already visited it, so we can skip it. + if (!flatten.add(current)) { + continue + } + + // Add all the included modules to the stack if they haven't been visited yet. + for (module in current.includedModules) { + if (module !in flatten) { + stack += module } } } From 052450707f3a19ea7a88d4db3f72e2481a682087 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Thu, 7 Mar 2024 15:35:54 +0700 Subject: [PATCH 09/15] update BenchmarkClass --- .../src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index 35c303f3d..fab5729eb 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -11,16 +11,17 @@ import org.openjdk.jmh.annotations.OutputTimeUnit import org.openjdk.jmh.annotations.Warmup import java.util.concurrent.TimeUnit +private val nestedModules = buildNestedModule( + depth = 128, + width = 256, +) + @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Fork(value = 2, warmups = 0) @Measurement(iterations = 5, time = 2) @Warmup(iterations = 1) open class BenchmarkClass { - private val nestedModules = buildNestedModule( - depth = 128, - width = 256, - ) @Benchmark fun emptyStart() { From a0eec9c1f825b93d338742754687c4b3b1e44473 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Thu, 7 Mar 2024 15:50:58 +0700 Subject: [PATCH 10/15] update benchmark --- .../org/koin/benchmark/BenchmarkClass.kt | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index fab5729eb..b2145f477 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -8,14 +8,12 @@ import org.openjdk.jmh.annotations.Fork import org.openjdk.jmh.annotations.Measurement import org.openjdk.jmh.annotations.Mode import org.openjdk.jmh.annotations.OutputTimeUnit +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State import org.openjdk.jmh.annotations.Warmup import java.util.concurrent.TimeUnit -private val nestedModules = buildNestedModule( - depth = 128, - width = 256, -) - @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Fork(value = 2, warmups = 0) @@ -45,12 +43,29 @@ open class BenchmarkClass { } @Benchmark - fun flattenRecursive() { - org.koin.core.module.flatten(nestedModules) + fun flattenRecursive(state: BenchmarkState) { + org.koin.core.module.flatten(state.nestedModules) } @Benchmark - fun flattenIterative() { - flattenIterative(nestedModules) + fun flattenIterative(state: BenchmarkState) { + flattenIterative(state.nestedModules) + } +} + +private val nestedModulesLazy = lazy { + buildNestedModule( + depth = 24, + width = 32, + ) +} + +@State(Scope.Benchmark) +open class BenchmarkState { + val nestedModules get() = nestedModulesLazy.value + + @Setup + fun prepare() { + nestedModulesLazy.value } } \ No newline at end of file From ce5a9c9762a36938b1a8e5fb01db030081128071 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Thu, 7 Mar 2024 15:53:56 +0700 Subject: [PATCH 11/15] reduce number due to StackOverflow exception :_) --- .../src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index b2145f477..d8fd29ac4 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -55,8 +55,8 @@ open class BenchmarkClass { private val nestedModulesLazy = lazy { buildNestedModule( - depth = 24, - width = 32, + depth = 10, + width = 20, ) } From e16e976a98b9b181ab1a0d9186a64c7758e25df4 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Thu, 7 Mar 2024 15:56:51 +0700 Subject: [PATCH 12/15] reduce number due to StackOverflow exception :_) --- .../src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index d8fd29ac4..4653b4d03 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -55,8 +55,8 @@ open class BenchmarkClass { private val nestedModulesLazy = lazy { buildNestedModule( - depth = 10, - width = 20, + depth = 8, + width = 10, ) } From 25f3579178298a20a595d1507b0266e92302fac6 Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Thu, 7 Mar 2024 17:03:34 +0700 Subject: [PATCH 13/15] update Benchmark --- .../org/koin/benchmark/BenchmarkClass.kt | 4 +- .../org/koin/benchmark/nestedModules.kt | 63 +------------------ 2 files changed, 4 insertions(+), 63 deletions(-) diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt index 4653b4d03..58a9f5013 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/BenchmarkClass.kt @@ -55,8 +55,8 @@ open class BenchmarkClass { private val nestedModulesLazy = lazy { buildNestedModule( - depth = 8, - width = 10, + depth = 10, + width = 100, ) } diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt index 2941f1e27..339082fc9 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt @@ -2,72 +2,14 @@ package org.koin.benchmark import org.koin.core.annotation.KoinInternalApi import org.koin.core.module.Module -import org.koin.core.qualifier.qualifier import org.koin.dsl.module internal fun buildNestedModule( depth: Int, width: Int, ): List { - val matrix = List(depth) { r -> - List(width) { c -> - val q = qualifier("q_${r}_$c") - module { - single(q) { A1() } - single(q) { A2() } - single(q) { A3() } - single(q) { A4() } - single(q) { A5() } - single(q) { A6() } - single(q) { A7() } - single(q) { A8() } - single(q) { A9() } - single(q) { A10() } - single(q) { A11() } - single(q) { A12() } - single(q) { A13() } - single(q) { A14() } - single(q) { A15() } - single(q) { A16() } - single(q) { A17() } - single(q) { A18() } - single(q) { A19() } - single(q) { A20() } - single(q) { A21() } - single(q) { A22() } - single(q) { A23() } - single(q) { A24() } - single(q) { A25() } - single(q) { A26() } - single(q) { A27() } - single(q) { A28() } - single(q) { A29() } - single(q) { A30() } - single(q) { A31() } - single(q) { A32() } - single(q) { A33() } - single(q) { A34() } - single(q) { A35() } - single(q) { A36() } - single(q) { A37() } - single(q) { A38() } - single(q) { A39() } - single(q) { A40() } - single(q) { A41() } - single(q) { A42() } - single(q) { A43() } - single(q) { A44() } - single(q) { A45() } - single(q) { A46() } - single(q) { A47() } - single(q) { A48() } - single(q) { A49() } - single(q) { A50() } - } - } - } + val matrix = List(depth) { List(width) { module {} } } - // Random a row that should be included in all modules. val level0Index = matrix.indices.random() val level0 = matrix[level0Index] @@ -79,14 +21,13 @@ internal fun buildNestedModule( ?.get(c) ?.let { module.includes(it) } - // Don't include itself. if (r != level0Index) { module.includes(level0) } } } - return matrix.last() + return matrix.first() } @OptIn(KoinInternalApi::class) From f34921fa8f0701bb3b6c6cbd173b3937f0e4eaee Mon Sep 17 00:00:00 2001 From: "hoc081098@gmail.com" Date: Thu, 7 Mar 2024 17:05:15 +0700 Subject: [PATCH 14/15] done --- examples/jvm-perfs/benchmark.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/jvm-perfs/benchmark.txt b/examples/jvm-perfs/benchmark.txt index 40b566670..7bb5d47f6 100644 --- a/examples/jvm-perfs/benchmark.txt +++ b/examples/jvm-perfs/benchmark.txt @@ -1,6 +1,6 @@ -Benchmark Mode Cnt Score Error Units -BenchmarkClass.emptyStart avgt 10 ≈ 10⁻³ ms/op -BenchmarkClass.flatten avgt 10 146.838 ± 15.775 ms/op -BenchmarkClass.newFlatten avgt 10 0.880 ± 0.018 ms/op -BenchmarkClass.start400 avgt 10 0.282 ± 0.005 ms/op -BenchmarkClass.start400AndInject avgt 10 0.278 ± 0.004 ms/op \ No newline at end of file +Benchmark Mode Cnt Score Error Units +BenchmarkClass.emptyStart avgt 10 ≈ 10⁻³ ms/op +BenchmarkClass.flattenIterative avgt 10 0.643 ± 0.112 ms/op +BenchmarkClass.flattenRecursive avgt 10 1.297 ± 0.043 ms/op +BenchmarkClass.start400 avgt 10 0.297 ± 0.053 ms/op +BenchmarkClass.start400AndInject avgt 10 0.283 ± 0.007 ms/op \ No newline at end of file From e6c432b75545a79e923f8105f6b02a0f0c78e9be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petrus=20Nguy=E1=BB=85n=20Th=C3=A1i=20H=E1=BB=8Dc?= Date: Thu, 7 Mar 2024 17:08:58 +0700 Subject: [PATCH 15/15] Apply suggestions from code review --- .../src/main/kotlin/org/koin/benchmark/nestedModules.kt | 2 +- .../src/commonMain/kotlin/org/koin/core/module/Module.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt index 339082fc9..34114ca94 100644 --- a/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt +++ b/examples/jvm-perfs/src/main/kotlin/org/koin/benchmark/nestedModules.kt @@ -32,7 +32,7 @@ internal fun buildNestedModule( @OptIn(KoinInternalApi::class) internal fun flattenIterative(modules: List): Set { - // This is essentially a DFS traversal of the module graph, + // This is actually a DFS traversal of the module graph, // but we're using a stack instead of recursion to avoid stack overflows and performance overhead. val flatten = HashSet() diff --git a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt index 9530a5448..fefb7459f 100644 --- a/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt +++ b/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt @@ -236,7 +236,7 @@ operator fun List.plus(module: Module): List = this + listOf(mod */ @OptIn(KoinInternalApi::class) fun flatten(modules: List): Set { - // This is essentially a DFS traversal of the module graph, + // This is actually a DFS traversal of the module graph, // but we're using a stack instead of recursion to avoid stack overflows and performance overhead. val flatten = HashSet()