Skip to content

Conversation

@GrabYourPitchforks
Copy link
Member

@GrabYourPitchforks GrabYourPitchforks commented Apr 6, 2021

Resolves #12832.

The managed reflection stack creates a defensive copy of the parameters object[] array on calls to MethodInfo.Invoke and ConstructorInfo.Invoke to prevent the parameter values changing between the initial type safety check and the real invocation of the target method. This PR special-cases "small" arrays (currently defined as <= 8 arguments) and creates a stack-based defensive copy instead of a heap-based defensive copy. This saves up to 88 bytes of heap allocations (on x64) per MethodInfo.Invoke or ConstructorInfo.Invoke call.

If 9 or more parameters are present, we fall back to the normal "allocate an array on the heap" code path.

Method Job Toolchain ArgCount Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
MethodInfoInvoke Job-ZXIRIY main 0 69.00 ns 1.142 ns 1.068 ns 1.00 0.00 - - - -
MethodInfoInvoke Job-TABAFA proto 0 64.27 ns 0.509 ns 0.451 ns 0.93 0.02 - - - -
ConstructorInfoInvoke Job-ZXIRIY main 0 90.13 ns 0.719 ns 0.672 ns 1.00 0.00 0.0029 - - 24 B
ConstructorInfoInvoke Job-TABAFA proto 0 91.63 ns 0.337 ns 0.298 ns 1.02 0.01 0.0029 - - 24 B
MethodInfoInvoke Job-ZXIRIY main 1 123.50 ns 2.510 ns 5.917 ns 1.00 0.00 0.0038 - - 32 B
MethodInfoInvoke Job-TABAFA proto 1 114.30 ns 1.356 ns 1.202 ns 0.93 0.04 - - - -
ConstructorInfoInvoke Job-ZXIRIY main 1 142.11 ns 1.344 ns 1.191 ns 1.00 0.00 0.0067 - - 56 B
ConstructorInfoInvoke Job-TABAFA proto 1 131.96 ns 1.575 ns 1.474 ns 0.93 0.01 0.0029 - - 24 B
MethodInfoInvoke Job-ZXIRIY main 8 358.50 ns 2.529 ns 2.365 ns 1.00 0.00 0.0105 - - 88 B
MethodInfoInvoke Job-TABAFA proto 8 358.44 ns 6.179 ns 5.478 ns 1.00 0.02 - - - -
ConstructorInfoInvoke Job-ZXIRIY main 8 383.78 ns 4.111 ns 3.846 ns 1.00 0.00 0.0134 - - 112 B
ConstructorInfoInvoke Job-TABAFA proto 8 377.16 ns 1.993 ns 1.864 ns 0.98 0.01 0.0029 - - 24 B
MethodInfoInvoke Job-ZXIRIY main 10 438.32 ns 5.554 ns 5.196 ns 1.00 0.00 0.0124 - - 104 B
MethodInfoInvoke Job-TABAFA proto 10 434.74 ns 3.087 ns 2.888 ns 0.99 0.01 0.0124 - - 104 B
ConstructorInfoInvoke Job-ZXIRIY main 10 454.83 ns 1.713 ns 1.431 ns 1.00 0.00 0.0153 - - 128 B
ConstructorInfoInvoke Job-TABAFA proto 10 465.04 ns 4.878 ns 4.563 ns 1.02 0.01 0.0153 - - 128 B

I also took this opportunity to refactor some code that was on the Invoke hot paths, such as lazy initialization of the InvocationFlags and Signature properties. These help offset some of the cost of setting up the struct-based argument holder.

@GrabYourPitchforks
Copy link
Member Author

GrabYourPitchforks commented Apr 6, 2021

Note to reviewers: This will be easier to review with hide whitespace changes set.

There's already significant unit and functional test coverage over all the reflection invocation code paths. I can't think offhand of any useful tests I could introduce alongside this change. But please feel free to offer suggestions and I can try to get them in.

private object? _arg6;
private object? _arg7;
#pragma warning restore CA1823, CS0169, IDE0051
}
Copy link
Member

Choose a reason for hiding this comment

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

We now have a few of these floating around, e.g.

private ref struct ThreeByteArrays
{
public const int NumItems = 3;
internal byte[] _item0;
private byte[] _item1;
private byte[] _item2;
}

private struct FourStackStrings // used to do the equivalent of: Span<string> strings = stackalloc string[4];
{
public string Item1;
public string Item2;
public string Item3;
public string Item4;
}

and other places we'd wanted to but just didn't go to the lengths of creating these (there's also the additional zero'ing overhead that comes if you overestimate). At some point, we should think about doing something more general, whether it be enabling runtime support for stackalloc to work with reference types, or language support to create these types for stackallocs with const sizes, or a set of helper types we simply share either in Common or even publicly.

@davidfowl
Copy link
Member

Can we add overloads to make these APIs support Span<object>, that would at least be a good first step.

@GrabYourPitchforks
Copy link
Member Author

@davidfowl Feel free to bring that to API review. Though I suspect you'll be more interested in the other "fast reflection" work being considered, which significantly cuts the remaining overhead from these methods. See other reflection issues assigned to Steve or me for more info.

@GrabYourPitchforks
Copy link
Member Author

GrabYourPitchforks commented Apr 12, 2021

Changes in latest update:

  • Knocked max stack allocation back from 8 to 4 elements. From what I've observed in the wild, most people pass a small number of parameters (usually 1 - 2) into the reflection APIs. If we get evidence that bumping this limit up would be beneficial, we could do that relatively easily.

  • Implemented Span<T> at the native layer per Jan's suggestion at Avoid intermediate allocations in MethodInfo/ConstructorInfo.Invoke #50814 (comment). I went a little further than what was minimally required for this scenario, as the logic introduced in object.h should be generalizable enough where we can immediately start using it in other code paths if desired.

My benchmark machine is currently producing lots of noise at the moment (WU, perhaps?). But as a rough estimate it looks about inline with the previous iteration.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

LGTM. Thank you!

@jkotas jkotas merged commit 880903b into dotnet:main Apr 13, 2021
@GrabYourPitchforks GrabYourPitchforks deleted the refl_mi_invoke_mem branch April 13, 2021 05:55
@ghost ghost locked as resolved and limited conversation to collaborators May 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Avoid allocation in object construction by reflection

6 participants