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

Reflection Constructor Binding Refactor #1155

Merged
merged 11 commits into from Jun 29, 2020

Conversation

alistairjevans
Copy link
Member

I've refactored/replaced the existing constructor binding logic to do more work up front and generally be faster.

Among key behaviour changes:

  • The binding process has been split into a ConstructorBinder, which at resolve time will 'bind' and output a BoundConstructor, which can then be used to instantiate the instance.
  • We now build the method used to resolve the constructor at container build time, rather than when it is first resolved. This means we don't have to hit the constructor delegate dictionary every time we resolve, only when we create the activator.
  • Reduced LINQ usage in the ReflectionActivator (removed Concat).

Some Benchmarks:

TLDR; This change improves across the board over v6, particularly with deep reflection graphs (DeepGraph is a full 1μs faster than develop now).

v6 is now generally faster than develop, apart from a few cases, particularly on concurrency where it still lags behind develop.

ChildScopeResolve

develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 50.65 us 0.395 us 0.350 us 11.9629 - - 49.07 KB

v6

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 35.98 μs 0.279 μs 0.261 μs 7.8125 - - 31.97 KB

constructor-binder

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 36.02 μs 0.358 μs 0.317 μs 7.7515 - - 31.85 KB

Concurrency

v6 is still generally slower on concurrency. That's one for another day.

develop

Method ResolveTaskCount ResolvesPerTask Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
MultipleResolvesOnMultipleTasks 100 100 2.529 ms 0.0140 ms 0.0131 ms 1234.3750 - - 4.9 MB

v6

Method ResolveTaskCount ResolvesPerTask Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
MultipleResolvesOnMultipleTasks 100 100 2.861 ms 0.0355 ms 0.0332 ms 1960.9375 - - 7.64 MB

constructor-binder

Method ResolveTaskCount ResolvesPerTask Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
MultipleResolvesOnMultipleTasks 100 100 2.828 ms 0.0476 ms 0.0422 ms 1960.9375 - - 7.64 MB

Decorators.KeylessSimple

develop

Method repetitions Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Baseline ? 592.2 ns 4.18 ns 3.49 ns 1.00 0.00 0.5083 - - 2.08 KB
ResolveEnumerableT 1 4,105.6 ns 18.88 ns 16.74 ns ? ? 1.5717 - - 6.44 KB
ResolveT 1 1,973.8 ns 10.16 ns 9.01 ns ? ? 0.9918 - - 4.05 KB
ResolveEnumerableT 2 7,380.3 ns 50.42 ns 44.70 ns ? ? 2.6398 - - 10.8 KB
ResolveT 2 3,296.3 ns 25.38 ns 23.74 ns ? ? 1.4763 - - 6.03 KB
ResolveEnumerableT 3 10,883.2 ns 66.92 ns 62.60 ns ? ? 3.6926 - - 15.16 KB
ResolveT 3 4,650.1 ns 24.56 ns 21.77 ns ? ? 1.9608 - - 8.01 KB

v6

Method repetitions Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Baseline ? 593.0 ns 3.99 ns 3.11 ns 1.00 0.00 0.5102 - - 2.09 KB
ResolveEnumerableT 1 4,506.0 ns 37.79 ns 35.35 ns ? ? 1.6785 - - 6.88 KB
ResolveT 1 2,076.4 ns 11.90 ns 10.55 ns ? ? 1.0414 - - 4.27 KB
ResolveEnumerableT 2 8,140.0 ns 69.90 ns 65.38 ns ? ? 2.8534 - - 11.68 KB
ResolveT 2 3,514.9 ns 10.28 ns 8.58 ns ? ? 1.5755 - - 6.45 KB
ResolveEnumerableT 3 11,660.5 ns 33.15 ns 27.68 ns ? ? 4.0283 - - 16.48 KB
ResolveT 3 4,899.3 ns 40.67 ns 36.06 ns ? ? 2.1057 - - 8.63 KB

constructor-binder

Method repetitions Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Baseline ? 591.6 ns 3.72 ns 3.48 ns 1.00 0.00 0.5102 - - 2.09 KB
ResolveEnumerableT 1 4,165.5 ns 21.89 ns 19.40 ns ? ? 1.6708 - - 6.85 KB
ResolveT 1 1,980.0 ns 13.46 ns 11.93 ns ? ? 1.0376 - - 4.25 KB
ResolveEnumerableT 2 7,437.9 ns 18.74 ns 14.63 ns ? ? 2.8381 - - 11.62 KB
ResolveT 2 3,267.1 ns 23.52 ns 22.00 ns ? ? 1.5678 - - 6.41 KB
ResolveEnumerableT 3 11,083.5 ns 82.16 ns 76.85 ns ? ? 3.9978 - - 16.38 KB
ResolveT 3 4,581.7 ns 54.62 ns 51.09 ns ? ? 2.0981 - - 8.58 KB

DeepGraphResolve

develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 16.23 us 0.066 us 0.058 us 4.4861 - - 18.45 KB

v6

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 16.39 μs 0.071 μs 0.063 μs 4.4861 - - 18.37 KB

constructor-binder

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Resolve 15.20 us 0.123 us 0.115 us 4.3182 - - 17.65 KB

RootContainerResolve

develop

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
OperatorNew 3.407 ns 0.0293 ns 0.0259 ns 1.00 0.00 0.0057 - - 24 B
NonSharedReflectionResolve 466.116 ns 2.5108 ns 2.2258 ns 136.81 1.14 0.1645 - - 688 B
NonSharedDelegateResolve 375.629 ns 2.7164 ns 2.2683 ns 110.25 1.10 0.1278 - - 536 B
SharedResolve 334.323 ns 1.6897 ns 1.5806 ns 98.14 0.93 0.1221 - - 512 B

v6

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
OperatorNew 3.443 ns 0.0254 ns 0.0225 ns 1.00 0.00 0.0057 - - 24 B
NonSharedReflectionResolve 453.595 ns 1.2737 ns 1.0636 ns 131.70 0.86 0.2332 - - 976 B
NonSharedDelegateResolve 377.809 ns 5.9404 ns 11.4452 ns 111.49 5.19 0.1969 - - 824 B
SharedResolve 323.010 ns 0.5395 ns 0.4212 ns 93.76 0.62 0.1912 - - 800 B

constructor-binder

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
OperatorNew 3.403 ns 0.0167 ns 0.0139 ns 1.00 0.00 0.0057 - - 24 B
NonSharedReflectionResolve 421.828 ns 1.7988 ns 1.5945 ns 123.91 0.76 0.2294 - - 960 B
NonSharedDelegateResolve 359.356 ns 0.9369 ns 0.7314 ns 105.59 0.41 0.1969 - - 824 B
SharedResolve 323.966 ns 1.1969 ns 0.9995 ns 95.20 0.60 0.1912 - - 800 B

OpenGeneric

develop

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ResolveOpenGeneric 491.7 ns 1.76 ns 1.56 ns 0.1640 - - 688 B

v6

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ResolveOpenGeneric 464.4 ns 3.02 ns 2.36 ns 0.2332 - - 976 B

constructor-binder

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ResolveOpenGeneric 436.6 ns 3.66 ns 3.42 ns 0.2294 - - 960 B

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.

I like what I'm seeing here but, as usual, have a couple of questions. Let me know what you think.

@alistairjevans
Copy link
Member Author

Oof, yeah, that MostParametersConstructorSelector was overdue for some love. Refactor is in, results based on the benchmark pending for develop:

develop

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Single 994.6 ns 4.53 ns 4.01 ns 1.00 0.00 0.3071 - - 1.26 KB
Two 2,112.8 ns 8.60 ns 7.18 ns 2.12 0.01 0.6638 - - 2.72 KB
Three 3,022.0 ns 9.73 ns 8.13 ns 3.04 0.01 0.9499 - - 3.88 KB
Four 3,893.8 ns 14.52 ns 12.12 ns 3.91 0.02 1.2970 - - 5.32 KB

constructor-binder

Method Mean Error StdDev Ratio Gen 0 Gen 1 Gen 2 Allocated
Single 893.8 ns 2.50 ns 1.95 ns 1.00 0.3576 - - 1.46 KB
Two 1,492.8 ns 6.43 ns 5.70 ns 1.67 0.5474 - - 2.24 KB
Three 2,182.4 ns 9.87 ns 8.75 ns 2.44 0.7820 - - 3.2 KB
Four 2,999.6 ns 12.39 ns 10.35 ns 3.36 1.0567 - - 4.32 KB

…luding any invalid ones.

This saves re-allocating a new array in the case of one invalid binding.
@alistairjevans
Copy link
Member Author

alistairjevans commented Jun 27, 2020

I've revisited how we allocate bindings, and re-allocate those invalid bindings, as you mentioned.

Where I've ended up is that the IConstructorSelector receives the original array of bindings rather than a filtered set. This means that the selectors are now responsible for ignoring invalid bindings.

This is obviously a breaking behaviour change, but the new requirement is a relatively easy one to integrate for that small set of users who have implemented a custom IConstructorSelector.

Memory and perf gains for the 'one of my constructors isn't valid' path:

develop

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Single 1.010 us 0.0069 us 0.0057 us 1.00 0.00 0.3071 - - 1.26 KB
Two 2.153 us 0.0161 us 0.0151 us 2.13 0.02 0.6638 - - 2.72 KB
TwoValidOneInvalid 2.738 us 0.0108 us 0.0101 us 2.71 0.02 0.8392 - - 3.44 KB
Three 3.041 us 0.0178 us 0.0158 us 3.01 0.03 0.9499 - - 3.88 KB
Four 3.917 us 0.0192 us 0.0170 us 3.88 0.03 1.2970 - - 5.32 KB

constructor-binder with filtering before-hand

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Single 893.1 ns 4.59 ns 4.07 ns 1.00 0.00 0.3576 - - 1.46 KB
Two 1,489.1 ns 9.34 ns 8.74 ns 1.67 0.01 0.5474 - - 2.24 KB
TwoValidOneInvalid 2,188.6 ns 17.92 ns 16.77 ns 2.45 0.02 0.7324 - - 2.99 KB
Three 2,171.2 ns 14.34 ns 13.41 ns 2.43 0.02 0.7820 - - 3.2 KB
Four 2,975.3 ns 18.54 ns 17.35 ns 3.33 0.02 1.0567 - - 4.32 KB

constructor-binder with selectors getting all bind results

Method Mean Error StdDev Ratio Gen 0 Gen 1 Gen 2 Allocated
Single 896.5 ns 4.61 ns 3.85 ns 1.00 0.3576 - - 1.46 KB
Two 1,511.2 ns 7.57 ns 7.08 ns 1.69 0.5474 - - 2.24 KB
TwoValidOneInvalid 1,955.7 ns 8.92 ns 8.34 ns 2.18 0.6866 - - 2.81 KB
Three 2,187.0 ns 9.01 ns 7.99 ns 2.44 0.7820 - - 3.2 KB
Four 2,964.0 ns 11.71 ns 10.96 ns 3.31 1.0567 - - 4.32 KB

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.

One minor typo and we're good. This is shaping up to be a really great v6!

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.

🦙

@tillig tillig merged commit f7275cd into autofac:v6 Jun 29, 2020
@alistairjevans alistairjevans added this to the v6.0 milestone Sep 26, 2020
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.

None yet

2 participants