Skip to content

Commit

Permalink
Merge pull request #1801 from hoc081098/hoc081098-patch-1
Browse files Browse the repository at this point in the history
flatten: use loop and ArrayDeque instead of recursion
  • Loading branch information
arnaudgiuliani authored Mar 29, 2024
2 parents 49ea507 + eab9b29 commit 15dc82e
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 10 deletions.
6 changes: 4 additions & 2 deletions examples/jvm-perfs/benchmark.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
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.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
2 changes: 1 addition & 1 deletion examples/jvm-perfs/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ tasks.getByName<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>("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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ 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.Scope
import org.openjdk.jmh.annotations.Setup
import org.openjdk.jmh.annotations.State
import org.openjdk.jmh.annotations.Warmup
import java.util.concurrent.TimeUnit


@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 2, warmups = 0)
Expand Down Expand Up @@ -33,4 +41,31 @@ open class BenchmarkClass {
}.koin
koinScenario(koin)
}

@Benchmark
fun flattenRecursive(state: BenchmarkState) {
org.koin.core.module.flatten(state.nestedModules)
}

@Benchmark
fun flattenIterative(state: BenchmarkState) {
flattenIterative(state.nestedModules)
}
}

private val nestedModulesLazy = lazy {
buildNestedModule(
depth = 10,
width = 100,
)
}

@State(Scope.Benchmark)
open class BenchmarkState {
val nestedModules get() = nestedModulesLazy.value

@Setup
fun prepare() {
nestedModulesLazy.value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.koin.benchmark

import org.koin.core.annotation.KoinInternalApi
import org.koin.core.module.Module
import org.koin.dsl.module

internal fun buildNestedModule(
depth: Int,
width: Int,
): List<Module> {
val matrix = List(depth) { List(width) { module {} } }

val level0Index = matrix.indices.random()
val level0 = matrix[level0Index]

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) }

if (r != level0Index) {
module.includes(level0)
}
}
}

return matrix.first()
}

@OptIn(KoinInternalApi::class)
internal fun flattenIterative(modules: List<Module>): Set<Module> {
// 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<Module>()
val stack = ArrayDeque(modules)

while (stack.isNotEmpty()) {
val current = stack.removeLast()

// 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
}
}
}

return flatten
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,27 @@ operator fun List<Module>.plus(module: Module): List<Module> = this + listOf(mod
*/
@OptIn(KoinInternalApi::class)
fun flatten(modules: List<Module>): Set<Module> {
fun flat(modules: List<Module>, newModules: MutableSet<Module>){
modules.forEach{
newModules += it
flat(it.includedModules,newModules)
// 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<Module>()
val stack = ArrayDeque(modules)

while (stack.isNotEmpty()) {
val current = stack.removeLast()

// 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
}
}
}
return mutableSetOf<Module>().apply { flat(modules,this) }

return flatten
}

0 comments on commit 15dc82e

Please sign in to comment.