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

Single constructor reflection optimisation #1325

Merged
merged 6 commits into from
Apr 13, 2022

Conversation

alistairjevans
Copy link
Member

@alistairjevans alistairjevans commented Apr 9, 2022

We didn't actually need to generate extra delegates using the mechanism added in #1321, I've added fast-paths through the reflection activator for single-constructor and matched-sig-constructor selection that does "early binding" of sorts on the constructors where possible.

The approach taken actually means I don't need to change the binding exception handling at all, and the existing resolve check behaviour stays.

We get noticeable perf and memory usage improvements across the board.

Some examples:

DeepGraphResolveBenchmark

# develop
|  Method |     Mean |    Error |   StdDev |  Gen 0 | Allocated |
|-------- |---------:|---------:|---------:|-------:|----------:|
| Resolve | 13.97 μs | 0.061 μs | 0.054 μs | 4.1351 |     17 KB |

# optimised
|  Method |     Mean |    Error |   StdDev |   Median |  Gen 0 | Allocated |
|-------- |---------:|---------:|---------:|---------:|-------:|----------:|
| Resolve | 12.82 μs | 0.254 μs | 0.451 μs | 12.45 μs | 3.5553 |     15 KB |

ChildScopeResolveBenchmark

# develop
|  Method |     Mean |    Error |   StdDev |  Gen 0 |  Gen 1 | Allocated |
|-------- |---------:|---------:|---------:|-------:|-------:|----------:|
| Resolve | 32.08 μs | 0.195 μs | 0.173 μs | 7.4463 | 1.4648 |     31 KB |

# optimised 
|  Method |     Mean |    Error |   StdDev |  Gen 0 |  Gen 1 | Allocated |
|-------- |---------:|---------:|---------:|-------:|-------:|----------:|
| Resolve | 31.47 μs | 0.084 μs | 0.070 μs | 7.3853 | 1.4648 |     30 KB |

EnumerableResolveBenchmark

# develop
|               Method |     Mean |     Error |    StdDev |  Gen 0 | Allocated |
|--------------------- |---------:|----------:|----------:|-------:|----------:|
|   ResolveIEnumerable | 3.066 μs | 0.0223 μs | 0.0186 μs | 0.9727 |      4 KB |
| ResolveIReadOnlyList | 3.070 μs | 0.0072 μs | 0.0064 μs | 0.9804 |      4 KB |
|         ResolveArray | 3.114 μs | 0.0106 μs | 0.0094 μs | 0.9727 |      4 KB |

# optimised
|               Method |     Mean |     Error |    StdDev |   Median |  Gen 0 | Allocated |
|--------------------- |---------:|----------:|----------:|---------:|-------:|----------:|
|   ResolveIEnumerable | 2.747 μs | 0.0122 μs | 0.0114 μs | 2.749 μs | 0.8125 |      3 KB |
| ResolveIReadOnlyList | 2.795 μs | 0.0189 μs | 0.0167 μs | 2.791 μs | 0.8202 |      3 KB |
|         ResolveArray | 2.787 μs | 0.0557 μs | 0.1175 μs | 2.704 μs | 0.8125 |      3 KB |

Multi-Constructor Benchmarks

# develop
|             Method |       Mean |   Error |  StdDev | Ratio |  Gen 0 | Allocated |
|------------------- |-----------:|--------:|--------:|------:|-------:|----------:|
|             Single |   813.9 ns | 2.08 ns | 1.84 ns |  1.00 | 0.3557 |      1 KB |
|                Two | 1,345.0 ns | 3.56 ns | 3.33 ns |  1.65 | 0.5455 |      2 KB |
| TwoValidOneInvalid | 1,684.5 ns | 6.15 ns | 5.75 ns |  2.07 | 0.6866 |      3 KB |
|              Three | 1,944.2 ns | 6.86 ns | 6.08 ns |  2.39 | 0.7782 |      3 KB |
|               Four | 2,568.4 ns | 8.19 ns | 7.66 ns |  3.16 | 1.0529 |      4 KB |

# optimised
|             Method |       Mean |    Error |  StdDev | Ratio | RatioSD |  Gen 0 | Allocated |
|------------------- |-----------:|---------:|--------:|------:|--------:|-------:|----------:|
|             Single |   735.8 ns |  2.58 ns | 2.16 ns |  1.00 |    0.00 | 0.3204 |      1 KB |
|                Two | 1,220.8 ns |  8.80 ns | 6.87 ns |  1.66 |    0.01 | 0.4921 |      2 KB |
| TwoValidOneInvalid | 1,555.0 ns |  4.23 ns | 3.96 ns |  2.11 |    0.01 | 0.6313 |      3 KB |
|              Three | 1,769.7 ns |  6.51 ns | 6.09 ns |  2.40 |    0.01 | 0.7000 |      3 KB |
|               Four | 2,359.6 ns | 10.00 ns | 8.86 ns |  3.21 |    0.02 | 0.9460 |      4 KB |

Improvements on the multi-constructor benchmarks (which use the old path) may be down to the use of the ReferenceEquals check against the NoParameters constant, rather than Any().

@codecov
Copy link

codecov bot commented Apr 9, 2022

Codecov Report

Merging #1325 (efa63ac) into develop (56af5b3) will increase coverage by 0.10%.
The diff coverage is 92.77%.

@@             Coverage Diff             @@
##           develop    #1325      +/-   ##
===========================================
+ Coverage    77.69%   77.80%   +0.10%     
===========================================
  Files          190      190              
  Lines         5412     5488      +76     
  Branches      1084     1097      +13     
===========================================
+ Hits          4205     4270      +65     
- Misses         705      710       +5     
- Partials       502      508       +6     
Impacted Files Coverage Δ
...Reflection/MatchingSignatureConstructorSelector.cs 72.50% <75.00%> (+1.66%) ⬆️
.../Core/Activators/Reflection/ReflectionActivator.cs 89.51% <96.42%> (+2.01%) ⬆️
src/Autofac/Core/Activators/InstanceActivator.cs 100.00% <100.00%> (ø)
...fac/Core/Activators/Reflection/BoundConstructor.cs 81.08% <100.00%> (+1.08%) ⬆️
...ac/Core/Activators/Reflection/ConstructorBinder.cs 91.93% <100.00%> (+0.13%) ⬆️
src/Autofac/Core/Lifetime/LifetimeScope.cs 81.94% <100.00%> (+0.12%) ⬆️
...rs/Reflection/MostParametersConstructorSelector.cs 92.30% <0.00%> (-7.70%) ⬇️
src/Autofac/Core/ResolvedParameter.cs 50.00% <0.00%> (-4.55%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 56af5b3...efa63ac. Read the comment docs.

Copy link
Member

@tillig tillig left a comment

Choose a reason for hiding this comment

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

Couple minor things, question about whether we should change the IConstructorSelector interface.

@alistairjevans
Copy link
Member Author

Added IConstructorSelectorWithEarlyBinding. Also made it optional for the SelectConstructorBinder function in that interface to return a ConstructorBinder instance. If it returns null, it falls back to the old selection mechanism at resolve time; means implementations can make "best effort" selection attempts.

Copy link
Member

@tillig tillig left a comment

Choose a reason for hiding this comment

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

Looking good. Only real question of substance is about whether IConstructorSelectorWithEarlyBinding should inherit IConstructorSelector.

Copy link
Member

@tillig tillig left a comment

Choose a reason for hiding this comment

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

🚨

@jimmycartrette
Copy link

Improvements on the multi-constructor benchmarks (which use the old path) may be down to the use of the ReferenceEquals check against the NoParameters constant, rather than Any().

Would it be because the multi-contructors still have dependencies, and those dependencies have single constructors which are now optimized?

@alistairjevans
Copy link
Member Author

Yes, that's also quite possible; I didn't really investigate the reason that closely.

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.

Feature Proposal - Generic Overloads for Register to make delegate registration more concise
3 participants