dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.44k stars 4.76k forks source link

DI allocations improvements #47607

Open sebastienros opened 3 years ago

sebastienros commented 3 years ago

In Orchard CMS which is quite allocaty, the second culprit is DI. This code path does a database request and renders the html. During that process several services are resolved, increasing the size of the scoped service dictionary.

Stack trace of allocations:

Name                                                                                                                                                                                                                                    Inc %
 Type Entry[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCacheKey,System.Object][]                                                                                                                                       7.8
+ coreclr!?                                                                                                                                                                                                                               7.8
|+ System.Private.CoreLib.il!System.Collections.Generic.Dictionary`2[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCacheKey,System.__Canon].Resize(int32,bool)                                                            7.8
||+ System.Private.CoreLib.il!System.Collections.Generic.Dictionary`2[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCacheKey,System.__Canon].TryInsert(!0,!1,value class System.Collections.Generic.InsertionBehavior)    7.8
|| + System.Private.CoreLib.il!System.Collections.Generic.Dictionary`2[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCacheKey,System.__Canon].Add(!0,!1)                                                                  7.8
||  + microsoft.extensions.dependencyinjection.il!dynamicClass.ResolveService(pMT: 00007FF84C1DFF08,pMT: 00007FF84C1D9B20)                                                                                                                7.8
||   + microsoft.extensions.dependencyinjection.il!dynamicClass.ResolveService(pMT: 00007FF84C1DFF08,pMT: 00007FF84C1D9B20)                                                                                                               7.8
||    + microsoft.extensions.dependencyinjection.abstractions.il!ServiceProviderServiceExtensions.GetRequiredService                                                                                                                      4.8
||    |+ microsoft.extensions.dependencyinjection.abstractions.il!ServiceProviderServiceExtensions.GetRequiredService                                                                                                                     4.8
||    | + orchardcore.displaymanagement.liquid!OrchardCore.DisplayManagement.Liquid.LiquidTemplateContextExtensions+<EnterScopeAsync>d__0.MoveNext()                                                                                      4.5

And overall memory consumption:

Name                                                                                                                                         Exc %  ExcInc %     Inc Fold                            When
?!?                                                                                                                                           8.66,57215.5  11,8571,7788389CDDoDCDFED9ECFFFDGGHFFE89DY*
e_sqlite3!?                                                                                                                                   4.03,088 6.1   4,7011,36762526767655664565557556565569535
microsoft.extensions.dependencyinjection.il!dynamicClass.ResolveService(pMT: 00007FF84C1DFF08,pMT: 00007FF84C1D9B20)                          2.92,217 5.4   4,1341,907105o556655665546566567566564630o
orchardcore.displaymanagement!OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay+<ExecuteAsync>d__7.MoveNext()                   2.72,09728.1  21,4691,95400N5WWXVWVVVWSOXUWWXXVWXWWXRWN9.
orchardcore.contentmanagement.display!OrchardCore.ContentManagement.Display.ContentItemDisplayCoordinator+<BuildDisplayAsync>d__9.MoveNext() 2.31,74910.0   7,6481,6760_81BCABBBABB99CBBACBABBBCCBD92_
System.Private.CoreLib.il!AsyncMethodBuilderCore.Start                                                                                        2.21,69071.9  54,9611,42801****************************3_
orchardcore.displaymanagement!OrchardCore.DisplayManagement.Views.ShapeResult+<ApplyImplementationAsync>d__17.MoveNext()                      2.01,540 5.2   4,0081,4130o40665656555446655655656666741_
orchardcore.displaymanagement.liquid!OrchardCore.DisplayManagement.Liquid.Tags.HelperStatement+<WriteToAsync>d__4.MoveNext()                  1.91,47413.0   9,9651,38200A3FGFFEEEEEDBGEEEFEEEFFGGCDA51
ntoskrnl!RtlpLookupFunctionEntryForStackWalks                                                                                                 1.91,437 1.9   1,437  242__o012222223221122222o222220____
orchardcore.displaymanagement.liquid!LiquidTagHelperActivator.Create                                                                          1.91,428 2.3   1,7831,3710o202222222222132232222233222110

This issue is just to raise some concern, and maybe there is nothing better to be done.

dotnet-issue-labeler[bot] commented 3 years ago

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

ghost commented 3 years ago

Tagging subscribers to this area: @eerhardt, @maryamariyan See info in area-owners.md if you want to be subscribed.

Issue Details
In Orchard CMS which is quite allocaty, the second culprit is DI. This code path does a database request and renders the html. During that process several services are resolved, increasing the size of the scoped service dictionary. Stack trace of allocations: ``` Name Inc % Type Entry[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCacheKey,System.Object][] 7.8 + coreclr!? 7.8 |+ System.Private.CoreLib.il!System.Collections.Generic.Dictionary`2[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCacheKey,System.__Canon].Resize(int32,bool) 7.8 ||+ System.Private.CoreLib.il!System.Collections.Generic.Dictionary`2[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCacheKey,System.__Canon].TryInsert(!0,!1,value class System.Collections.Generic.InsertionBehavior) 7.8 || + System.Private.CoreLib.il!System.Collections.Generic.Dictionary`2[Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCacheKey,System.__Canon].Add(!0,!1) 7.8 || + microsoft.extensions.dependencyinjection.il!dynamicClass.ResolveService(pMT: 00007FF84C1DFF08,pMT: 00007FF84C1D9B20) 7.8 || + microsoft.extensions.dependencyinjection.il!dynamicClass.ResolveService(pMT: 00007FF84C1DFF08,pMT: 00007FF84C1D9B20) 7.8 || + microsoft.extensions.dependencyinjection.abstractions.il!ServiceProviderServiceExtensions.GetRequiredService 4.8 || |+ microsoft.extensions.dependencyinjection.abstractions.il!ServiceProviderServiceExtensions.GetRequiredService 4.8 || | + orchardcore.displaymanagement.liquid!OrchardCore.DisplayManagement.Liquid.LiquidTemplateContextExtensions+d__0.MoveNext() 4.5 ``` And overall memory consumption: ``` Name Exc % ExcInc % Inc Fold When ?!? 8.66,57215.5 11,8571,7788389CDDoDCDFED9ECFFFDGGHFFE89DY* e_sqlite3!? 4.03,088 6.1 4,7011,36762526767655664565557556565569535 microsoft.extensions.dependencyinjection.il!dynamicClass.ResolveService(pMT: 00007FF84C1DFF08,pMT: 00007FF84C1D9B20) 2.92,217 5.4 4,1341,907105o556655665546566567566564630o orchardcore.displaymanagement!OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay+d__7.MoveNext() 2.72,09728.1 21,4691,95400N5WWXVWVVVWSOXUWWXXVWXWWXRWN9. orchardcore.contentmanagement.display!OrchardCore.ContentManagement.Display.ContentItemDisplayCoordinator+d__9.MoveNext() 2.31,74910.0 7,6481,6760_81BCABBBABB99CBBACBABBBCCBD92_ System.Private.CoreLib.il!AsyncMethodBuilderCore.Start 2.21,69071.9 54,9611,42801****************************3_ orchardcore.displaymanagement!OrchardCore.DisplayManagement.Views.ShapeResult+d__17.MoveNext() 2.01,540 5.2 4,0081,4130o40665656555446655655656666741_ orchardcore.displaymanagement.liquid!OrchardCore.DisplayManagement.Liquid.Tags.HelperStatement+d__4.MoveNext() 1.91,47413.0 9,9651,38200A3FGFFEEEEEDBGEEEFEEEFFGGCDA51 ntoskrnl!RtlpLookupFunctionEntryForStackWalks 1.91,437 1.9 1,437 242__o012222223221122222o222220____ orchardcore.displaymanagement.liquid!LiquidTagHelperActivator.Create 1.91,428 2.3 1,7831,3710o202222222222132232222233222110 ``` This issue is just to raise some concern, and maybe there is nothing better to be done.
Author: sebastienros
Assignees: -
Labels: `area-Extensions-DependencyInjection`, `tenet-performance`, `untriaged`
Milestone: -
davidfowl commented 3 years ago

The thing that stands out to me is the dictionary resize. We could potentially improve this by specifying a capacity like max(scoped/2, 30) something like this (I picked those numbers out of thin air)

eerhardt commented 3 years ago

@sebastienros - are there instructions on how to run this locally to investigate?

sebastienros commented 3 years ago

Here is how I do it:

I can help more if you need. I can also provide this scenario as a crank command.

davidfowl commented 3 years ago

I was discussing this with @maryamariyan and one thing we might want to try is re-using the scoped dictionary. This makes some assumption that scopes look the same and we have no idea to identify similar scopes (say by an id) so we can try it out with a small pool (say number of size of number of cores). This won't help for long running scopes in things like Blazor server, or Orleans but will do 2 things:

We might also want to keep the dictionaries to a reasonable size so we don't bloat memory (maybe calling TrimExcess if we reached some number of entries threshold).

davidfowl commented 3 years ago

Re-opening this as I reverted the change. I've added an event to the event source so we can figure out just how heterogeneous or homogenous scopes are within the same container.

eerhardt commented 3 years ago

Moving to 7.0 since this isn't a "must have" for 6.0.