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

Split scope resolution logic into seperate dynamic methods #570

Merged
merged 11 commits into from
Nov 29, 2018

Conversation

pakrym
Copy link

@pakrym pakrym commented Nov 27, 2018

Goal is to decrease method size in cases of large service trees with a lot of scoped services.

This change is based on #546.
I'll rebase once it goes in.

Fixes dotnet/aspnetcore#3054 and dotnet/aspnetcore#2737

Perf doesn't seem to be impacted.

@pakrym pakrym changed the title [WIP] Split scope resolution logic into seperate dynamic methods Split scope resolution logic into seperate dynamic methods Nov 27, 2018
@pakrym
Copy link
Author

pakrym commented Nov 28, 2018

Some perf numbers:

Before:


      Method |        Mode |        Mean |      Error |      StdDev |         Op/s | Allocated |
------------ |------------ |------------:|-----------:|------------:|-------------:|----------:|
      Scoped |     Dynamic |    78.37 ns |  1.9784 ns |   2.0317 ns | 12,760,581.9 |       0 B |
 LargeScoped |     Dynamic | 3,368.88 ns | 64.6920 ns |  60.5129 ns |    296,834.2 | 9200000 B |
      Scoped | Expressions |    79.04 ns |  0.2384 ns |   0.1991 ns | 12,651,093.5 |       0 B |
 LargeScoped | Expressions | 3,369.41 ns | 63.6107 ns |  59.5015 ns |    296,787.7 | 9200000 B |
      Scoped |      ILEmit |    78.58 ns |  1.1371 ns |   0.9495 ns | 12,725,924.2 |       0 B |
 LargeScoped |      ILEmit | 4,648.14 ns | 91.3279 ns | 105.1734 ns |    215,139.7 | 9200000 B |
      Scoped |     Runtime |   105.83 ns |  1.5913 ns |   1.4106 ns |  9,448,873.0 |       0 B |
 LargeScoped |     Runtime | 1,970.86 ns | 39.0219 ns |  57.1978 ns |    507,393.2 | 9200000 B |

After:


      Method |        Mode |        Mean |      Error |     StdDev |         Op/s | Allocated |
------------ |------------ |------------:|-----------:|-----------:|-------------:|----------:|
      Scoped |     Dynamic |    76.04 ns |  0.7249 ns |  0.6781 ns | 13,150,404.5 |       0 B |
 LargeScoped |     Dynamic | 1,059.56 ns | 19.9302 ns | 18.6427 ns |    943,787.1 | 9200000 B |
      Scoped | Expressions |    79.50 ns |  1.7264 ns |  2.0552 ns | 12,579,365.1 |       0 B |
 LargeScoped | Expressions | 1,031.06 ns |  3.1943 ns |  2.9879 ns |    969,879.3 | 9200000 B |
      Scoped |      ILEmit |    79.00 ns |  0.5764 ns |  0.5109 ns | 12,658,282.6 |       0 B |
 LargeScoped |      ILEmit | 1,019.38 ns |  3.2009 ns |  2.6729 ns |    980,989.3 | 9200000 B |
      Scoped |     Runtime |   100.78 ns |  0.5367 ns |  0.4758 ns |  9,922,939.0 |       0 B |
 LargeScoped |     Runtime | 1,919.32 ns | 25.6269 ns | 23.9714 ns |    521,018.4 | 9200000 B |

Simple case didn't regress while large tree case improved 4x just because of reduced method size.

var info = ILEmitCallSiteAnalyzer.Instance.CollectGenerationInfo(callSite);
var runtimeContext = GenerateMethodBody(callSite, dynamicMethod.GetILGenerator(info.Size), info);

#if SAVE_ASSEMBLY
Copy link
Member

Choose a reason for hiding this comment

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

Do we have a test for this? Apparently this should have been SAVE_ASSEMBLIES even before this change.

Copy link
Author

Choose a reason for hiding this comment

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

This is a feature I only use when debugging code IL code generation it's always excluded from shipping builds.

return scope => _runtimeResolver.Resolve(callSite, scope);
private GeneratedMethod BuildType(ServiceCallSite callSite)
{
// Only scope methods are cached
Copy link
Member

Choose a reason for hiding this comment

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

I'm probably missing something obvious, but why not cache GeneratedMethods for transient services?

Copy link
Member

Choose a reason for hiding this comment

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

I think I remember now. It's because the methods that don't need to check the ResolvedServices dictionary are already small enough. Still, if there's no perf impact of splitting scoped service instantiation into individual methods, it might be worth trying for non-scoped services.

Copy link
Author

Choose a reason for hiding this comment

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

My current thinking is that there is no perf impact because scope resolution methods themselves are comparably slow. There is also not much benefit in caching transient resolvers because most of them contain a single method call anyway.

@pakrym pakrym merged commit 1424884 into master Nov 29, 2018
@natemcmaster natemcmaster deleted the pakrym/split-scopes branch December 4, 2018 19:56
maryamariyan pushed a commit to maryamariyan/runtime that referenced this pull request Feb 28, 2020
maryamariyan pushed a commit to maryamariyan/runtime that referenced this pull request Mar 2, 2020
maryamariyan pushed a commit to maryamariyan/runtime that referenced this pull request Mar 11, 2020
maryamariyan pushed a commit to maryamariyan/runtime that referenced this pull request Mar 27, 2020
@ghost ghost locked as resolved and limited conversation to collaborators May 30, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants