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 improvements #147

Merged
merged 9 commits into from
May 28, 2021
Merged

Perf improvements #147

merged 9 commits into from
May 28, 2021

Conversation

Yortw
Copy link
Contributor

@Yortw Yortw commented May 16, 2021

What

These changes improve performance of the TinyIocContainer, mostly for the Resolve method calls but also some minor improvements to AutoRegister. Most (but not all) performance improvements to Resolve follow the existing patterns (for compiled expressions etc) where a small performance penalty may be paid on the first call to resolve a registration due to caching/compilation etc. but subsequent resolutions of the same registration will be faster. Also includes a new project in the solution (under the tests folder) using BenchmarkDotNet to confirm that any changes made are actually an improvement.

Also while I'm here I would like to thank GrumpyDev and everyone else who works on this project. TinyIoC is my go to IoC for most projects, it's simple, fast, light weight and just works. It's also easy to debug through when I screw up registrations etc. I love it! Thank you :)

Why

TinyIoC has always been "fast enough" for me and I've never worried about it's performance before. However I'm currently trying to get a Xamarin Forms app to work on a specific piece of hardware running a custom Android OS and performance of the app is abysmal. I am therefore trying to optimize every part of the app and although I don't think TinyIoC is a significant part of my problem it seemed like there might be some easy wins here so I put in some effort to see if I could make it go faster.

Possibly breaking change

All of the changes to improve the performance of the Resolve method are intended to be 'safe' and 'non-breaking'. All existing unit tests still pass (no new tests added).

However commit 10a2be1 for improving the AutoRegister method has a potentially breaking change where it doesn't register some types it used to. Specifically types that are nested and private are no longer registered. While this is potentially a breaking change I think it is a good one for several reasons:

  • Private and nested classes by definition should only be visible and constructable by their parent, so using an IoC container to instantiate them seems unlikely (probably won't break anybody) and overkill (if it does break somebody they should perhaps reconsider their design and/or perform manual registration if required)
  • This can eliminate a large number of types that compiler generated, such as the 'DisplayClass' types hose generated for async/await handling, which would never be desirable to construct via IoC anyway. The result is improved performance of autoregister and less memory consumed.

There are a few options here;

  • Remove/do not merge this commit, if the breaking change is undesirable.
  • Accept/merge the change and hope for the best (🙄😲😬)
  • Accept/merge the commit but document the change and bump the major version of TinyIoC (semantic version style) so consumers know to check the docs/expect a behaviour change.
  • Further modify the code so it only excludes types that are nested and private and marked with the CompilerGenerated attribute. This wouldn't have as big a performance improvement, but would reduce the chance of breaking consumers while still eliminating many of the types generated by the compiler that just don't need to be registered.

Performance Improvements

Below are the results of the improvements made, as measured using Benchmark dot net. The size of the improvement varies by platform and use case, but as you can see in some cases the improvements are signficant.

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-6820HQ CPU 2.70GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
  [Host]     : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
  Job-XIDDXQ : Mono 6.12.0 (Visual Studio), X86 
  .NET 4.6.1 : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
  .NET 4.8   : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
  RyuJitX64  : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
  .NET Core 3.1 : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X86 RyuJIT
  RyuJitX86     : .NET Core 3.1.15 (CoreCLR 4.700.21.21202, CoreFX 4.700.21.21402), X86 RyuJIT

Jit=RyuJit  Platform=X86  Runtime=.NET Core 3.1  

Jit=RyuJit  Platform=X64    
Method Job Runtime Mean Error StdDev Median Ratio Gen 0 Gen 1 Gen 2 Allocated
Original_Resolve_Singleton Job-XIDDXQ Mono 381.8 ns 1.72 ns 1.53 ns 381.4 ns 1.00 0.0248 - - -
New_Resolve_Singleton Job-XIDDXQ Mono 350.4 ns 1.15 ns 1.07 ns 350.6 ns 0.92 0.0248 - - -
Original_Resolve_Instance_Without_Dependencies Job-XIDDXQ Mono 3,711.3 ns 19.35 ns 17.15 ns 3,716.8 ns 1.00 0.2289 - - -
New_Resolve_Instance_Without_Dependencies Job-XIDDXQ Mono 1,052.2 ns 5.32 ns 4.72 ns 1,052.9 ns 0.28 0.0477 - - -
Original_Resolve_Instance_With_Singleton_Dependency Job-XIDDXQ Mono 7,713.7 ns 109.28 ns 102.22 ns 7,691.2 ns 1.00 0.4883 - - -
New_Resolve_Instance_With_Singleton_Dependency Job-XIDDXQ Mono 2,065.8 ns 9.16 ns 8.57 ns 2,065.1 ns 0.27 0.1183 - - -
Original_Resolve_OpenGeneric Job-XIDDXQ Mono 7,021.9 ns 32.62 ns 30.51 ns 7,026.0 ns 1.00 0.3433 - - -
New_Resolve_OpenGeneric Job-XIDDXQ Mono 4,062.2 ns 21.18 ns 18.77 ns 4,058.4 ns 0.58 0.2441 - - -
Original_Resolve_Unregistered Job-XIDDXQ Mono 7,945.2 ns 62.07 ns 55.02 ns 7,947.2 ns 1.00 0.4883 - - -
New_Resolve_Unregistered Job-XIDDXQ Mono 2,215.0 ns 11.08 ns 10.36 ns 2,213.3 ns 0.28 0.1144 - - -
Original_Resolve_AutomaticFuncFactory Job-XIDDXQ Mono 72,855.8 ns 1,038.93 ns 971.82 ns 73,138.1 ns 1.00 0.9766 - - -
New_Resolve_AutomaticFuncFactory Job-XIDDXQ Mono 2,116.3 ns 8.71 ns 7.72 ns 2,118.4 ns 0.03 0.1068 - - -
Original_Resolve_Singleton .NET 4.6.1 .NET 4.6.1 204.8 ns 0.66 ns 0.59 ns 204.8 ns 1.00 0.0305 - - 128 B
New_Resolve_Singleton .NET 4.6.1 .NET 4.6.1 182.2 ns 0.96 ns 0.89 ns 182.2 ns 0.89 0.0305 - - 128 B
Original_Resolve_Instance_Without_Dependencies .NET 4.6.1 .NET 4.6.1 2,042.9 ns 9.99 ns 9.34 ns 2,046.9 ns 1.00 0.2441 - - 1035 B
New_Resolve_Instance_Without_Dependencies .NET 4.6.1 .NET 4.6.1 528.9 ns 3.22 ns 3.02 ns 528.0 ns 0.26 0.0553 - - 233 B
Original_Resolve_Instance_With_Singleton_Dependency .NET 4.6.1 .NET 4.6.1 4,448.2 ns 18.32 ns 15.29 ns 4,449.7 ns 1.00 0.5569 - - 2367 B
New_Resolve_Instance_With_Singleton_Dependency .NET 4.6.1 .NET 4.6.1 1,162.8 ns 4.20 ns 3.93 ns 1,162.7 ns 0.26 0.1659 - - 698 B
Original_Resolve_OpenGeneric .NET 4.6.1 .NET 4.6.1 3,518.8 ns 13.22 ns 12.36 ns 3,521.4 ns 1.00 0.3738 - - 1573 B
New_Resolve_OpenGeneric .NET 4.6.1 .NET 4.6.1 1,190.6 ns 4.56 ns 3.81 ns 1,190.6 ns 0.34 0.2174 - - 915 B
Original_Resolve_Unregistered .NET 4.6.1 .NET 4.6.1 4,482.3 ns 32.26 ns 28.60 ns 4,482.9 ns 1.00 0.5569 - - 2359 B
New_Resolve_Unregistered .NET 4.6.1 .NET 4.6.1 1,224.1 ns 7.32 ns 6.49 ns 1,223.3 ns 0.27 0.1640 - - 690 B
Original_Resolve_AutomaticFuncFactory .NET 4.6.1 .NET 4.6.1 192,627.3 ns 762.55 ns 636.76 ns 192,366.5 ns 1.000 1.2207 0.4883 - 6044 B
New_Resolve_AutomaticFuncFactory .NET 4.6.1 .NET 4.6.1 657.1 ns 11.03 ns 18.13 ns 648.5 ns 0.003 0.1030 - - 433 B
Original_Resolve_Singleton .NET 4.8 .NET 4.8 203.4 ns 0.57 ns 0.51 ns 203.3 ns 1.00 0.0305 - - 128 B
New_Resolve_Singleton .NET 4.8 .NET 4.8 181.2 ns 0.70 ns 0.62 ns 181.2 ns 0.89 0.0305 - - 128 B
Original_Resolve_Instance_Without_Dependencies .NET 4.8 .NET 4.8 2,082.3 ns 11.73 ns 10.97 ns 2,081.3 ns 1.00 0.2441 - - 1035 B
New_Resolve_Instance_Without_Dependencies .NET 4.8 .NET 4.8 532.0 ns 10.58 ns 12.99 ns 525.3 ns 0.26 0.0553 - - 233 B
Original_Resolve_Instance_With_Singleton_Dependency .NET 4.8 .NET 4.8 4,456.6 ns 34.89 ns 29.14 ns 4,451.5 ns 1.00 0.5569 - - 2367 B
New_Resolve_Instance_With_Singleton_Dependency .NET 4.8 .NET 4.8 1,157.9 ns 7.26 ns 6.43 ns 1,156.6 ns 0.26 0.1659 - - 698 B
Original_Resolve_OpenGeneric .NET 4.8 .NET 4.8 3,573.7 ns 14.60 ns 13.66 ns 3,572.7 ns 1.00 0.3738 - - 1573 B
New_Resolve_OpenGeneric .NET 4.8 .NET 4.8 1,168.9 ns 5.07 ns 4.74 ns 1,167.9 ns 0.33 0.2174 - - 915 B
Original_Resolve_Unregistered .NET 4.8 .NET 4.8 4,503.4 ns 29.47 ns 26.13 ns 4,502.9 ns 1.00 0.5569 - - 2359 B
New_Resolve_Unregistered .NET 4.8 .NET 4.8 1,222.3 ns 7.94 ns 7.43 ns 1,222.8 ns 0.27 0.1640 - - 690 B
Original_Resolve_AutomaticFuncFactory .NET 4.8 .NET 4.8 193,410.2 ns 1,539.04 ns 1,439.62 ns 193,072.4 ns 1.000 1.2207 0.4883 - 6044 B
New_Resolve_AutomaticFuncFactory .NET 4.8 .NET 4.8 649.8 ns 2.71 ns 2.54 ns 650.1 ns 0.003 0.1030 - - 433 B
Original_Resolve_Singleton RyuJitX64 .NET 4.8 201.5 ns 0.67 ns 0.63 ns 201.4 ns 1.00 0.0305 - - 128 B
New_Resolve_Singleton RyuJitX64 .NET 4.8 177.9 ns 0.46 ns 0.39 ns 177.9 ns 0.88 0.0305 - - 128 B
Original_Resolve_Instance_Without_Dependencies RyuJitX64 .NET 4.8 2,092.5 ns 24.72 ns 20.64 ns 2,093.1 ns 1.00 0.2441 - - 1035 B
New_Resolve_Instance_Without_Dependencies RyuJitX64 .NET 4.8 526.0 ns 3.89 ns 3.64 ns 527.0 ns 0.25 0.0553 - - 233 B
Original_Resolve_Instance_With_Singleton_Dependency RyuJitX64 .NET 4.8 4,442.0 ns 23.17 ns 21.68 ns 4,443.3 ns 1.00 0.5569 - - 2367 B
New_Resolve_Instance_With_Singleton_Dependency RyuJitX64 .NET 4.8 1,190.3 ns 23.29 ns 26.82 ns 1,178.8 ns 0.27 0.1659 - - 698 B
Original_Resolve_OpenGeneric RyuJitX64 .NET 4.8 3,690.3 ns 21.22 ns 17.72 ns 3,686.1 ns 1.00 0.3738 - - 1573 B
New_Resolve_OpenGeneric RyuJitX64 .NET 4.8 1,173.6 ns 6.32 ns 5.60 ns 1,173.9 ns 0.32 0.2174 - - 915 B
Original_Resolve_Unregistered RyuJitX64 .NET 4.8 4,506.9 ns 40.82 ns 38.18 ns 4,503.9 ns 1.00 0.5569 - - 2359 B
New_Resolve_Unregistered RyuJitX64 .NET 4.8 1,178.0 ns 3.88 ns 3.63 ns 1,178.0 ns 0.26 0.1640 - - 690 B
Original_Resolve_AutomaticFuncFactory RyuJitX64 .NET 4.8 193,420.0 ns 784.40 ns 733.73 ns 193,337.1 ns 1.000 1.2207 0.4883 - 6043 B
New_Resolve_AutomaticFuncFactory RyuJitX64 .NET 4.8 634.2 ns 2.42 ns 2.27 ns 633.5 ns 0.003 0.1030 - - 433 B
Original_Resolve_Singleton .NET Core 3.1 .NET Core 3.1 211.9 ns 0.86 ns 0.71 ns 211.9 ns 1.00 0.0229 - - 96 B
New_Resolve_Singleton .NET Core 3.1 .NET Core 3.1 199.7 ns 1.55 ns 1.45 ns 199.9 ns 0.94 0.0229 - - 96 B
Original_Resolve_Instance_Without_Dependencies .NET Core 3.1 .NET Core 3.1 1,246.9 ns 9.14 ns 8.55 ns 1,245.3 ns 1.00 0.1392 - - 584 B
New_Resolve_Instance_Without_Dependencies .NET Core 3.1 .NET Core 3.1 463.9 ns 4.92 ns 4.60 ns 465.1 ns 0.37 0.0381 - - 160 B
Original_Resolve_Instance_With_Singleton_Dependency .NET Core 3.1 .NET Core 3.1 2,699.8 ns 12.95 ns 11.48 ns 2,698.5 ns 1.00 0.3242 - - 1368 B
New_Resolve_Instance_With_Singleton_Dependency .NET Core 3.1 .NET Core 3.1 1,020.0 ns 15.05 ns 11.75 ns 1,017.1 ns 0.38 0.1163 - - 488 B
Original_Resolve_OpenGeneric .NET Core 3.1 .NET Core 3.1 2,264.7 ns 9.28 ns 8.68 ns 2,265.1 ns 1.00 0.2365 - - 1000 B
New_Resolve_OpenGeneric .NET Core 3.1 .NET Core 3.1 1,010.7 ns 15.28 ns 37.77 ns 996.6 ns 0.45 0.2003 - - 844 B
Original_Resolve_Unregistered .NET Core 3.1 .NET Core 3.1 2,669.0 ns 8.64 ns 8.08 ns 2,670.6 ns 1.00 0.3242 - - 1360 B
New_Resolve_Unregistered .NET Core 3.1 .NET Core 3.1 1,027.1 ns 8.28 ns 7.34 ns 1,024.5 ns 0.38 0.1144 - - 480 B
Original_Resolve_AutomaticFuncFactory .NET Core 3.1 .NET Core 3.1 149,309.8 ns 2,887.31 ns 2,835.72 ns 148,600.8 ns 1.000 0.7324 0.2441 - 3320 B
New_Resolve_AutomaticFuncFactory .NET Core 3.1 .NET Core 3.1 620.0 ns 11.62 ns 14.70 ns 613.9 ns 0.004 0.0868 - - 364 B
Original_Resolve_Singleton RyuJitX86 RyuJitX86 227.0 ns 4.61 ns 4.31 ns 225.4 ns 1.00 0.0229 - - 96 B
New_Resolve_Singleton RyuJitX86 RyuJitX86 199.2 ns 3.93 ns 4.52 ns 197.3 ns 0.88 0.0229 - - 96 B
Original_Resolve_Instance_Without_Dependencies RyuJitX86 RyuJitX86 1,253.8 ns 8.63 ns 8.07 ns 1,253.5 ns 1.00 0.1392 - - 584 B
New_Resolve_Instance_Without_Dependencies RyuJitX86 RyuJitX86 476.6 ns 5.08 ns 4.75 ns 477.3 ns 0.38 0.0381 - - 160 B
Original_Resolve_Instance_With_Singleton_Dependency RyuJitX86 RyuJitX86 2,703.9 ns 19.78 ns 16.52 ns 2,700.6 ns 1.00 0.3242 - - 1368 B
New_Resolve_Instance_With_Singleton_Dependency RyuJitX86 RyuJitX86 1,039.1 ns 8.15 ns 7.22 ns 1,040.8 ns 0.38 0.1163 - - 488 B
Original_Resolve_OpenGeneric RyuJitX86 RyuJitX86 2,260.5 ns 9.06 ns 7.56 ns 2,262.7 ns 1.00 0.2365 - - 1000 B
New_Resolve_OpenGeneric RyuJitX86 RyuJitX86 972.5 ns 4.71 ns 4.41 ns 972.1 ns 0.43 0.2003 - - 844 B
Original_Resolve_Unregistered RyuJitX86 RyuJitX86 2,617.5 ns 31.03 ns 29.03 ns 2,609.5 ns 1.00 0.3242 - - 1360 B
New_Resolve_Unregistered RyuJitX86 RyuJitX86 1,020.5 ns 2.67 ns 2.50 ns 1,019.6 ns 0.39 0.1144 - - 480 B
Original_Resolve_AutomaticFuncFactory RyuJitX86 RyuJitX86 139,243.0 ns 1,209.08 ns 1,071.81 ns 139,072.4 ns 1.000 0.7324 0.2441 - 3320 B
New_Resolve_AutomaticFuncFactory RyuJitX86 RyuJitX86 576.6 ns 3.32 ns 2.94 ns 576.7 ns 0.004 0.0868 - - 364 B

Copy link
Collaborator

@niemyjski niemyjski left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! This looks good to me but I'd like to have someone else also give some feedback/eyes.

@niemyjski niemyjski requested a review from grumpydev May 19, 2021 11:27
@niemyjski
Copy link
Collaborator

@gkarabin care to review this as well :)

@gkarabin
Copy link
Contributor

It's on my weekend reading list! I will have a hard time looking at it for long for a few days, but I will review it as I can.

@gkarabin
Copy link
Contributor

I'm a few commits into the review at this point. So far things look excellent from a functional point of view, but I need to do some of the commits on a computer (GitHub is not great on iPadOS).

One general comment is that there's quite a bit of unnecessary white space changing to this point. It's nothing that will prevent completing the review, but it does slow things down checking for possible differences. Whether or not you want to rebase and adjust the commits is up to you.

@Yortw
Copy link
Contributor Author

Yortw commented May 23, 2021

My apologies for the whitespace changes, I tried hard not to reformat things just because but my IDE obviously has different defaults configured and there wasn't an editor config setup to enforce consistency so I may have inadvertently adjusted portions I changed trying to get it look right locally, forgetting that was going to cause an issue. This is only my second(?) PR to an open source project so it's likely I've made a few errors like this, sorry.
I appreciate everyone taking the time to review.
If there's someway I can tidy up the whitespace for you I'd be happy to do it but may need a hand guided through the process... I don't normally use git and rebasing is something I'm unfamiliar with, and I'm not sure of exactly how to edit the commits to get the desired outcome either.

Copy link
Contributor

@gkarabin gkarabin left a comment

Choose a reason for hiding this comment

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

Other than whitespace changes, I don't see any problems. Each commit makes sense, as do the tests.

@gkarabin
Copy link
Contributor

My apologies for the whitespace changes, I tried hard not to reformat things just because but my IDE obviously has different defaults configured and there wasn't an editor config setup to enforce consistency so I may have inadvertently adjusted portions I changed trying to get it look right locally, forgetting that was going to cause an issue. This is only my second(?) PR to an open source project so it's likely I've made a few errors like this, sorry.
I appreciate everyone taking the time to review.
If there's someway I can tidy up the whitespace for you I'd be happy to do it but may need a hand guided through the process... I don't normally use git and rebasing is something I'm unfamiliar with, and I'm not sure of exactly how to edit the commits to get the desired outcome either.

Rebasing (interactively) is probably a tricky thing to ask for if you aren't familiar with the process. My favorite explanation is here (see "Splitting a Commit"). You can use the "edit" command on a commit and then use an editor in which you can see and remove the whitespace changes. Then you re-commit the change (pasting the previous commit notice), and let the rebase continue.

Sometimes there are "merge" conflicts reapplying subsequent commits. Handling them can be very confusing. If you run into many of them on your first rebase you may well give up. Once you get the hang of it, it's manageable, but it is an art.

If you choose to try it out, if I were you, I would create a new branch off of your existing work for experimenting with the process. If you want to be extra careful, you might even choose to do it from another clone of the TinyIoC repo, to avoid steps that could mess up your "good" repo. I rebase commits most every day without issues or extra safety measures, but when I was learning I occasionally made impressive mistakes that left me hunting for "the old version" in dangling commits so I could start over.

@niemyjski niemyjski merged commit f70e466 into grumpydev:master May 28, 2021
@niemyjski
Copy link
Collaborator

Thanks for the PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants