grumpydev / TinyIoC

An easy to use, hassle free, Inversion of Control Container for small projects, libraries and beginners alike.
MIT License
830 stars 235 forks source link

Perf improvements #147

Closed Yortw closed 3 years ago

Yortw commented 3 years ago

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 10a2be1de102ce7fcd7a72f07e5c496c5afbe946 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:

There are a few options here;

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
niemyjski commented 3 years ago

@gkarabin care to review this as well :)

gkarabin commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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.

gkarabin commented 3 years ago

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 commented 3 years ago

Thanks for the PR!