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

Memoize lazily evaluated sequences #2953

Merged
merged 3 commits into from
Jan 8, 2020

Conversation

HebaruSan
Copy link
Member

@HebaruSan HebaruSan commented Dec 25, 2019

Background

In #2928 I learned how yield return works; yes, it pauses execution of the function until we request the next element of the sequence, but if you try to access earlier members of the sequence, the whole function will start over again from the beginning, re-running any expensive logic that we were hoping to skip. In other words, it's lazily-evaluated but not memoizing.

This is particularly inconvenient when we want to retrieve and use a sequence and take a special action when it's empty. The idiomatic approach is to use .Any() followed by a foreach, but this suffers from the duplicate execution problem described above; any expensive steps that we take at the start of the function will be repeated (including network requests!). #2928 worked around this with a bool foundAny, which is kind of painful to read.

Changes

Now a new .Memoize() LINQ-style function is added to CKAN.Extensions based on some work from StackOverflow. It acts as a memoizing wrapper around another enumerable. Each item of the upstream sequence is requested once and only once the first time a downstream consumer requests it, after which it is stored in a list, from which any subsequent requests for the same item are served without accessing the upstream enumerable. This will allow us to use lazy evaluation safely.

This function is used to clean up some instances I found of duplicated execution of enumerables (I searched for .Any() and checked whether there was a foreach later for the same variable). If you find more, I'd be happy to add more fixes here.

Separate but related, I noticed when running GUI with --debug that we seem to check a lot of module compatibilities over and over. I think this is because AvailableModule.Latest's use of LastOrDefault requires iterating over its whole sequence. Now we Reverse the sequence (which can be done quickly for a SortedDictionary) and use FirstOrDefault instead to reduce the number of modules checked.

@HebaruSan HebaruSan added Bug GUI Issues affecting the interactive GUI Core (ckan.dll) Issues affecting the core part of CKAN Pull request labels Dec 25, 2019
@DasSkelett
Copy link
Member

Separate but related, I noticed when running GUI with --debug that we seem to check a lot of module compatibilities over and over. I think this is because AvailableModule.Latest's use of LastOrDefault requires iterating over its whole sequence. Now we Reverse the sequence (which can be done quicly for a SortedDictionary) and use FirstOrDefault instead to reduce the number of modules checked.

This does reduce the startup time immensely, nice job!
I think we can reduce it by almost one more calculation per identifier:

diff --git a/GUI/GUIMod.cs b/GUI/GUIMod.cs
index 06198755..69649ed2 100644
--- a/GUI/GUIMod.cs
+++ b/GUI/GUIMod.cs
@@ -196,9 +196,6 @@ public GUIMod(CkanModule mod, IRegistryQuerier registry, KspVersionCriteria curr
         public GUIMod(string identifier, IRegistryQuerier registry, KspVersionCriteria current_ksp_version, bool? incompatible = null)
         {
             Identifier     = identifier;
-            IsIncompatible = incompatible
-                ?? registry.AllAvailable(identifier)
-                    .All(m => !m.IsCompatibleKSP(current_ksp_version));
             IsAutodetected = registry.IsAutodetected(identifier);
             DownloadCount  = registry.DownloadCount(identifier);
             if (registry.IsAutodetected(identifier))
@@ -216,6 +213,8 @@ public GUIMod(string identifier, IRegistryQuerier registry, KspVersionCriteria c
             {
             }
 
+            IsIncompatible = incompatible ?? LatestCompatibleMod is null;
+
             // Let's try to find the compatibility for this mod. If it's not in the registry at
             // all (because it's a DarkKAN mod) then this might fail.
 

The debug output will go from (master)

3790 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding all available versions for AlcubierreStandalone
3790 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3797 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3797 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3797 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.1.1 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.1.2 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.2.1 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.3.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.3.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.2.1 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.3.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.4.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3798 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.3.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.4.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.5.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.6.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.6.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.6.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.7.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.8.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.9.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.9.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.10.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.1.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.2.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3799 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3799 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3801 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3802 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.1.1 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.1.2 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.2.1 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.3.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.3.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.2.1 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.3.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.4.4.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3802 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.3.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.4.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.5.5.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.6.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.6.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.6.2.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.7.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.8.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.9.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.9.1.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 0.10.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.1.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.2.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3803 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]

over (your branch)

3927 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding all available versions for AlcubierreStandalone
3928 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3934 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3935 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3935 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3935 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3935 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3937 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3938 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3938 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]

to (with my diff applied)

3856 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3857 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3858 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]
3864 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3864 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3867 [Thread Pool Worker] DEBUG CKAN.Registry (null) - Finding latest available for AlcubierreStandalone
3867 [Thread Pool Worker] DEBUG CKAN.AvailableModule (null) - Our dictionary has 31 keys
3867 [Thread Pool Worker] DEBUG CKAN.CkanModule (null) - Testing if AlcubierreStandalone 1.3.0.0 is compatible with KSP [Versions: 1.8.1.2694, 1.8]

@HebaruSan
Copy link
Member Author

I like it!!

@DasSkelett
Copy link
Member

DasSkelett commented Jan 8, 2020

I'm just gonna drop the file names + line numbers where my IDE reports Possible multiple enumeration. Some of them seem to be valid, some not:

List of reports
  • CKAN-GUI
    • MainChangeset.cs:98,106 - changes
    • MainInstall.cs:123,125 - recRows
    • MainLabel.cs:134,152 - mods
  • CKAN-core
    • ModuleInstaller.cs:733,739,741 - mods

    • ModuleInstaller.cs:742,744 - revdep

    • ModuleInstaller.cs:749,756,780 - goners

    • ModuleInstaller.cs:988,991,1001 - remove

    • ModuleInstaller.cs:989,999 - add

    • ModuleInstaller.cs:1038,1047,1084 - modules

    • ModuleInstaller.cs:1101,1115 - replacements

    • SanityChecker.cs:85,86 - modules + dlls

    • SanityChecker.cs:109,113 - modules

    • SanityChecker.cs:142,146 - modules

    • SanityChecker.cs:149 - others

    • RelationshipResolver.cs:316,321,327 - old_stanza

    • RelationshipResolver.cs:352,479 - stanza

    • Registry.cs:1154,1156 - installedModules

    • Registry.cs:1087,1090,1098,1100,1107,1113 - modules_to_remove

    • Registry.cs:1097,1100 - orig_installed

    • Registry.cs:1100,1103 - dlls

    • Registry.cs:767,794,800 - relative_files

    • AvailableModule.cs:117,117,126 - others

    • AvailableModule.cs:130 - othersMinusSelf

Feel free to edit this comment to remove invalid ones or add more or whatever.

@HebaruSan
Copy link
Member Author

Nice! Latest commit should address all of those.

Copy link
Member

@DasSkelett DasSkelett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, startup and instance switching is so much faster now!
Generally CKAN feels much more responsive, this is amazing.

The code for the Memoize type looks reasonable, and everything seems to still work ;) 👍

HebaruSan added a commit that referenced this pull request Jan 8, 2020
@HebaruSan HebaruSan merged commit 6e11ef6 into KSP-CKAN:master Jan 8, 2020
@HebaruSan HebaruSan deleted the fix/ienumerable branch January 8, 2020 19:42
@HebaruSan
Copy link
Member Author

I feel like the universe was reminding me that paying attention to weird output and tracking it down is almost always rewarded. 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Core (ckan.dll) Issues affecting the core part of CKAN GUI Issues affecting the interactive GUI Pull request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants