Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Avoid unnecessary initialization in nested lazy initializers #15279

Closed
mcomella opened this issue Sep 21, 2020 · 10 comments
Closed

Avoid unnecessary initialization in nested lazy initializers #15279

mcomella opened this issue Sep 21, 2020 · 10 comments
Assignees
Labels
performance Possible performance wins

Comments

@mcomella
Copy link
Contributor

mcomella commented Sep 21, 2020

MarcLeclair found a pattern that prevents kotlin's lazy initialization pattern from working as expected in nested lazy initializers when combined with our serviceLocator system:

    val useCases by lazy {
        UseCases(
            search.searchEngineManager,
        )
    }

via code sample

If useCases is referenced and thus initialized, search.searchEngineManager will unexpectedly be initialized because it's referenced to pass it into the useCases constructor, even if it's not actually used in the initializer. In practice, we've identified improvements of up to 60-80ms in just one case where this occurred (in useCases).

We should try to identify a generic solution to prevent this anti-pattern for good.

Initial solution ideas

  • we can wrap Lazy, or implement our own, to intelligently pass getters rather than the actual object
  • maybe we can adapt this one-off solution that ac has used in the past (definition, usage)
  • as a last ditch effort, we could implement static analysis to try to prevent this from occurring but it'll add a layer of complexity possibly unlike other solutions

┆Issue is synchronized with this Jira Task

@mcomella mcomella added the performance Possible performance wins label Sep 21, 2020
@mcomella mcomella added this to Needs prioritization in Performance, front-end roadmap via automation Sep 21, 2020
@github-actions github-actions bot added the needs:triage Issue needs triage label Sep 21, 2020
@mcomella
Copy link
Contributor Author

Baron-Severin and I had an interesting discussion: with nested lazy initialization, we can later get a performance issue where, say we're moving through a fragment transaction and trigger a cascading initialization of many components, causing us to drop frames. Given this can be triggered by random code paths, we may not notice this in local development but our users may notice it.

This is no reason not to fix this bug – being in an indeliberate half lazy state is just as bad if not worse – but we should consider a solution for this in the future – I filed https://jira.mozilla.com/browse/FXP-1358 to consider implementing this in the future.

@kbrosnan kbrosnan removed the needs:triage Issue needs triage label Sep 24, 2020
@MarcLeclair MarcLeclair moved this from Needs prioritization to Backlog (prioritized) in Performance, front-end roadmap Oct 7, 2020
@mcomella mcomella self-assigned this Oct 26, 2020
@mcomella mcomella moved this from Backlog (prioritized) to In progress in Performance, front-end roadmap Oct 26, 2020
@mcomella
Copy link
Contributor Author

I wrote up a document with my proposed solution: https://docs.google.com/document/d/12O0elEeo5zOBqFwWJ0mZmh4L2d-UNZrnQe5ZrPdj1_o/edit#heading=h.kvxbynlqzbyf I wanted to get feedback before landing it because it affects how the fenix team interacts with components.

@mcomella
Copy link
Contributor Author

mcomella commented Oct 31, 2020

I have most of a PR ready to add a code to count the number of components we initialize but it's built on changes in this PR #16101 which is blocked by CI from landing. Also, it'd benefit from changes in MarcLeclair's PR #15942

@mcomella
Copy link
Contributor Author

I have most of a PR ready to add a code to count the number of components we initialize

I want to count the number of components we initialize before we start doing recursive init protection to see how much of an impact the recursive init protection actually makes.

mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
…t groups.

By component groups, I mean I applied this to any class with the
class kdoc, "Component group for...".

There are a few instances of lazy we had to keep using the old API to
avoid having to update constructor arguments.
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
…-in API use.

By having LazyMonitored implement Lazy, we can continue to pass these
values directly into the ac APIs that require Lazy references. For some
reason, implementing `Lazy.value` can replace `operator fun getValue`
required for delegates.
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
…APIs.

They're currently lazy { lazy { value } }. Accessing `lazy.value`
directly allows us to make it lazy { value }. This should be more
performant and prevents us from double-counting these components.
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
…t groups.

By component groups, I mean I applied this to any class with the
class kdoc, "Component group for...".

There are a few instances of lazy we had to keep using the old API to
avoid having to update constructor arguments.
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
…-in API use.

By having LazyMonitored implement Lazy, we can continue to pass these
values directly into the ac APIs that require Lazy references. For some
reason, implementing `Lazy.value` can replace `operator fun getValue`
required for delegates.
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
…APIs.

They're currently lazy { lazy { value } }. Accessing `lazy.value`
directly allows us to make it lazy { value }. This should be more
performant and prevents us from double-counting these components.
mcomella added a commit to mcomella/fenix that referenced this issue Oct 31, 2020
@mcomella
Copy link
Contributor Author

I have most of a PR ready to add a code to count the number of components we initialize

#16292

After this, we need to add a test for counting the number of components init so we don't regress, then started working on the recursive lazy implementation.

mcomella added a commit to mcomella/fenix that referenced this issue Nov 2, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Nov 2, 2020
…t groups.

By component groups, I mean I applied this to any class with the
class kdoc, "Component group for...".

There are a few instances of lazy we had to keep using the old API to
avoid having to update constructor arguments.
mcomella added a commit to mcomella/fenix that referenced this issue Nov 2, 2020
…-in API use.

By having LazyMonitored implement Lazy, we can continue to pass these
values directly into the ac APIs that require Lazy references. For some
reason, implementing `Lazy.value` can replace `operator fun getValue`
required for delegates.
mcomella added a commit to mcomella/fenix that referenced this issue Nov 2, 2020
…APIs.

They're currently lazy { lazy { value } }. Accessing `lazy.value`
directly allows us to make it lazy { value }. This should be more
performant and prevents us from double-counting these components.
mcomella added a commit to mcomella/fenix that referenced this issue Nov 2, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Nov 2, 2020
mcomella added a commit to mcomella/android-components that referenced this issue Nov 2, 2020
mcomella added a commit to mcomella/android-components that referenced this issue Nov 6, 2020
…<WebAppShortcutManager>.

My goal is to convert `UseCases` to use entirely LazyComponents because
it has known performance issues related to recursive lazy initialization.
This will act as a proof-of-concept for this change.

This is commit mozilla-mobile#1 for changing the `webAppShortcutManager` to a
LazyComponent in fenix.
mcomella added a commit to mcomella/android-components that referenced this issue Nov 6, 2020
…nent WebAppShortcutManager.

This is commit mozilla-mobile#2 of making WebAppShortcutManager a Lazy Component.
mcomella added a commit to mcomella/android-components that referenced this issue Nov 6, 2020
…rch... in LazyComponent.

This is needed for fenix to convert searchEngineManager to
LazyComponent.

Unfortunately, even though I didn't add a parameter, detekt is now
warning for a long parameter list. Since that's outside the scope of
this PR, I suppressed it.
mcomella added a commit to mcomella/android-components that referenced this issue Nov 6, 2020
mcomella added a commit to mcomella/android-components that referenced this issue Nov 6, 2020
…<WebAppShortcutManager>.

My goal is to convert `UseCases` to use entirely LazyComponents because
it has known performance issues related to recursive lazy initialization.
This will act as a proof-of-concept for this change.

This is commit mozilla-mobile#1 for changing the `webAppShortcutManager` to a
LazyComponent in fenix.
mcomella added a commit to mcomella/android-components that referenced this issue Nov 6, 2020
…nent WebAppShortcutManager.

This is commit mozilla-mobile#2 of making WebAppShortcutManager a Lazy Component.
mcomella added a commit to mcomella/android-components that referenced this issue Nov 6, 2020
…rch... in LazyComponent.

This is needed for fenix to convert searchEngineManager to
LazyComponent.

Unfortunately, even though I didn't add a parameter, detekt is now
warning for a long parameter list. Since that's outside the scope of
this PR, I suppressed it.
mcomella added a commit to mcomella/android-components that referenced this issue Nov 6, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Nov 6, 2020
…n object.

This keeps it consistent with LazyComponent and helps avoid confusion
when accessing their values (i.e. ComponentInitCount can be confusing if
it only represents LazyMonitored).
mcomella added a commit to mcomella/fenix that referenced this issue Nov 6, 2020
Something random I noticed that should be changed.
mcomella added a commit to mcomella/fenix that referenced this issue Nov 6, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Nov 6, 2020
…rceUseTest.

I ran into this issue when developing this PR and I thought it would be
more intuitive in this order.
mcomella added a commit to mcomella/fenix that referenced this issue Nov 6, 2020
This commit series must be landed in collaboration with the changes
destined for ac.

This is the first commit towards turning `UseCases` into using
LazyComponents.
mcomella added a commit to mcomella/fenix that referenced this issue Nov 6, 2020
…omponent.

This is part 2 of moving UseCases to LazyComponents.

Our first success! We have one less component initialized and one fewer
runBlocking calls.
@mcomella
Copy link
Contributor Author

mcomella commented Nov 6, 2020

I put up two PRs as proof-of-concept using LazyComponent: mozilla-mobile/android-components#8909 #16410

It was a lot of work to use it so I'm wondering if it's worth the effort to keep going forward. I should probably measure the difference in start up time from this PR.

@mcomella
Copy link
Contributor Author

We're having a discussion on the best approach in the ac PR: mozilla-mobile/android-components#8909 (comment)

@mcomella mcomella moved this from In progress to Waiting in Performance, front-end roadmap Nov 11, 2020
@mcomella mcomella moved this from Waiting to In progress in Performance, front-end roadmap Dec 9, 2020
mcomella added a commit to mcomella/android-components that referenced this issue Dec 10, 2020
…<WebAppShortcutManager>.

My goal is to convert `UseCases` to use entirely LazyComponents because
it has known performance issues related to recursive lazy initialization.
This will act as a proof-of-concept for this change.

This is commit mozilla-mobile#1 for changing the `webAppShortcutManager` to a
LazyComponent in fenix.
mcomella added a commit to mcomella/android-components that referenced this issue Dec 10, 2020
…nent WebAppShortcutManager.

This is commit mozilla-mobile#2 of making WebAppShortcutManager a Lazy Component.
mcomella added a commit to mcomella/android-components that referenced this issue Dec 10, 2020
…rch... in LazyComponent.

This is needed for fenix to convert searchEngineManager to
LazyComponent.

Unfortunately, even though I didn't add a parameter, detekt is now
warning for a long parameter list. Since that's outside the scope of
this PR, I suppressed it.
mcomella added a commit to mcomella/android-components that referenced this issue Dec 10, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Dec 10, 2020
…n object.

This keeps it consistent with LazyComponent and helps avoid confusion
when accessing their values (i.e. ComponentInitCount can be confusing if
it only represents LazyMonitored).
mcomella added a commit to mcomella/fenix that referenced this issue Dec 10, 2020
mcomella added a commit to mcomella/fenix that referenced this issue Dec 10, 2020
…rceUseTest.

I ran into this issue when developing this PR and I thought it would be
more intuitive in this order.
mcomella added a commit to mcomella/fenix that referenced this issue Dec 10, 2020
This commit series must be landed in collaboration with the changes
destined for ac.

This is the first commit towards turning `UseCases` into using
LazyComponents.
@mcomella
Copy link
Contributor Author

The LazyComponent proof-of-concept turned out to be unnecessary because the searchEngineManager, which had the performance problems, was refactored out in a separate PR. See mozilla-mobile/android-components#8909 (comment) for details.

If we find other components with perf issues that we can't optimize out, we can use this approach there. However, there are no actionable items to do right now without further investigation of performance issues during start up so let's close this issue.

Performance, front-end roadmap automation moved this from In progress to Done Dec 10, 2020
@mcomella
Copy link
Contributor Author

To be explicit, we made no perf optimizations in this PR but we came up with an approach to deal with under-performing components.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
performance Possible performance wins
Development

No branches or pull requests

2 participants