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

perf The module flattening function can reduce GC using MutableSet #1640

Merged
merged 4 commits into from
Sep 7, 2023

Conversation

KAMO030
Copy link
Contributor

@KAMO030 KAMO030 commented Aug 28, 2023

The module flattening function can reduce GC using MutableSet

image

If "tailrec" is not necessary, it can be more concise and efficient

no tailrec + use MutableSet Fun:

fun flatModules(modules: List<Module>, newModules: MutableSet<Module> = mutableSetOf()): Set<Module> {
    return newModules.apply {
        modules.forEach {
            this += it
            flatModules(it.includedModules, this)
        }
    }
}

@KAMO030 KAMO030 marked this pull request as draft August 29, 2023 12:09
@arnaudgiuliani arnaudgiuliani self-requested a review August 30, 2023 08:58
@arnaudgiuliani

This comment was marked as resolved.

@arnaudgiuliani arnaudgiuliani added this to the android-3.5.0 milestone Aug 30, 2023
@arnaudgiuliani arnaudgiuliani marked this pull request as ready for review August 30, 2023 10:23
@arnaudgiuliani arnaudgiuliani changed the base branch from main to 3.5.0 August 30, 2023 10:24
@1250422131
Copy link

When we use the newModules.add(head) method, although it reduces the need to create a new collection, we are actually modifying the original collection newModules. This means that we are changing the state of the original collection by adding the new element head to it.

In contrast, using the newModules + head operator creates a new collection object that contains all the elements of the original collection newModules along with the new element head. This operation does not modify the original collection; instead, it returns a new collection object.

By using newModules + head, we adhere to the principle of not modifying the original collection and avoid potential side effects. The benefit of doing this is that we can create a new collection object in each iteration without affecting the newModules collection passed as input. This helps ensure the reliability and maintainability of the code.

Therefore, using the newModules + head operator instead of the newModules.add(head) method follows the principles of functional programming, avoids side effects, and maintains code clarity and readability.

However, newModules.add(head) is also fine. It does indeed avoid creating new objects and saves memory overhead. We just need to make sure to communicate this to future developers, so they won't be confused when working with this code.

@XLZJBFZ
Copy link

XLZJBFZ commented Aug 30, 2023

We can use queue to traverse modules. At the beginning, we add the modules to the queue. Each time, we take a module out of the queue and add it to the set, while also add the included modules from this module to the queue until the queue is empty. Using queue instead of recursion can reduce GC while not changing the original module set.

fun flatten(modules: List<Module>, newModules: Set<Module> = emptySet()): Set<Module> {
    val queue: Queue<Module> = LinkedList(modules)
    val flattenedModules = newModules.toMutableSet()
    while (queue.isNotEmpty()) {
        queue.poll().run {
            this.includedModules.forEach {
                queue.offer(it)
            }
            flattenedModules += this
        }
    }
    return flattenedModules.toSet()
}

@arnaudgiuliani arnaudgiuliani marked this pull request as draft August 30, 2023 14:12
@KAMO030 KAMO030 marked this pull request as ready for review August 31, 2023 00:45
@KAMO030
Copy link
Contributor Author

KAMO030 commented Aug 31, 2023

Intersting, any statistics behind this?

203 modules with a statistical deep of 10 :

no tailrec + use MutableSet:286
Before use Set:9495
After use MutableSet:6488

I think without 'tailrec' and using 'MutableSet' is a better choice
Because both solve the consumption caused by "head. importedModules+tail" and "newModules+head"
of course, provided that 'tailrec' is not necessary
and Removing the default value "newModules" of function input parameters can prevent external unsafe calls

fun flatten(modules: List<Module>): Set<Module> {
    fun flat(modules: List<Module>, newModules: MutableSet<Module>){
        modules.forEach{
            newModules += it
            flat(it.includedModules,newModules)
        }
    }
    return mutableSetOf<Module>().apply { flat(modules,this) }
}

@arnaudgiuliani
Copy link
Member

arnaudgiuliani commented Aug 31, 2023

in this case, I would tend to say that tail rec is not mandatory. The call graph is not big enough in memory I guess 🤔
Even an app with few hundreds of modules, the recursion is not super deep.

Instead of overriding the current function, we can write a second one and use it for a moment. We need to observe the gain. Some of you could help make test on a beta version of Koin 3.5?

@KAMO030
Copy link
Contributor Author

KAMO030 commented Sep 1, 2023

in this case, I would tend to say that tail rec is not mandatory. The call graph is not big enough in memory I guess 🤔 Even an app with few hundreds of modules, the recursion is not super deep.

Instead of overriding the current function, we can write a second one and use it for a moment. We need to observe the gain. Some of you could help make test on a beta version of Koin 3.5?

No problem, Do I need to recommit the 'no tailrec' version of the code?

@arnaudgiuliani
Copy link
Member

yep, update your proposal with best no tailrec approach 👍
thanks

@KAMO030
Copy link
Contributor Author

KAMO030 commented Sep 1, 2023

yep, update your proposal with best no tailrec approach 👍
thanks

ok,is updated

@arnaudgiuliani
Copy link
Member

PR #1643 has been merged. Can you update your PR a last time?

# Conflicts:
#	core/koin-core/src/commonMain/kotlin/org/koin/core/module/Module.kt
@KAMO030
Copy link
Contributor Author

KAMO030 commented Sep 5, 2023

PR #1643 has been merged. Can you update your PR a last time?

How should I update PR

@arnaudgiuliani
Copy link
Member

that's ok, I thought there were remaining conflicts 👍

@arnaudgiuliani arnaudgiuliani merged commit 7cd52f0 into InsertKoinIO:3.5.0 Sep 7, 2023
7 of 8 checks passed
@arnaudgiuliani
Copy link
Member

Good job, and thanks all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status:wait_feedback type:improvement Improving a current feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants