-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Improve startup and warmup performance for invoke by reducing emit usage #109901
base: main
Are you sure you want to change the base?
Conversation
…flectionFunctionPointers
…flectionFunctionPointers
src/libraries/System.Private.CoreLib/src/System/Reflection/ConstructorInvoker.cs
Outdated
Show resolved
Hide resolved
@steveharter do you plan to add support for other well known signatures than the zero arg with return value and one arg with void return ones? Based on our past discussion, we would like to eventually add ones that are on startup path of common types of apps like e.g. aspnet ones. Or were these mostly just those two basic cases? |
…flectionFunctionPointers
That approach (zero arg + one arg) was for property getters\setters however it didn't work for instance methods since function pointers don't support instance methods (sometimes it worked for simple methods, but other times it cause heap corruption) so I had to change it to very-hard-coded list of specific types and methods used during startup. That could be extended for other types based on the applications we want to check that are sensitive to startup\warmup (e.g. ASP.NET, WinForms) However, that is somewhat untenable so we should really get function pointers to support instance methods, so I created the language proposal at dotnet/csharplang#8709. This would automatically bring in a ton of all methods, perhaps up to 50%, since it would cover most property getters and setters and other simple methods. Another approach, compatible with the function pointer + instance methods above, is to add an API to let the application specify their own delegate for method that they do not want to cause emit\jit. E.g.: public class MethodBase
{
// We would support only our built-in delegates that are for property getters\setters, <= 4 args, byref args, etc.
+ void SetInvokeImplementation(Delegate delegate);
}
|
We can emit the function-pointer based thunks for arbitrary instance methods as runtime intrinsics. We do not need first class C# support for instance method function pointers to make it work. |
…flectionFunctionPointers
…flectionFunctionPointers
|
||
public static int GetHashCode(Type? declaringType, Type[] parameterTypes, Type returnType) | ||
{ | ||
int hashcode = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this impl can be simplified by using HashCode
. The explicit use of int.Rotate*
to construct a new hash seems unnecessary when we have utilities to combine and construct a well defined new hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HashCode
is randomized hashcode. For this one, I think we may be rather going towards non-randomized stable hashcode so that would allow us to create hashtable of precompiled signatures at build time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. That makes sense. Perhaps a comment on that expectation is warranted then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently the hashcode is based on combining several calls to System.Type.GetHashCode()
, so it is not stable. It would have to use Type.FullName
. I can make that change here, but since it not needed yet I think I should take the original suggestion of using HashCode.Combine()
-- I checked that perf against current (rotate \ xor) and it's a wash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also with a Type.FullName
hashcode, I'd have to find or implement a fast stable version of that since the default is randomized.
public Type ReturnType => _returnType; | ||
public bool IsStatic => _isStatic; | ||
|
||
public static bool AlternativeEquals(in InvokeSignatureInfoKey @this, InvokeSignatureInfo signatureInfo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid the @this
pattern. It is rarely used in SPCL and I personally dislike it. The english language has enough words that we can avoid using keywords. In this case, I would suggest inst
, instance
, or key
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thanks.
This increases startup and warmup performance by:
Call
opcode to theCallI
opcode when possible.CallI
uses a function pointer as a stack parameter whileCall
(andCallVirt
) use a method handle as an operand. This meansCalli
can re-use an emitted method as long as the signature is compatible whileCall
uses a method handle as an operand meaning the handle is embedded in the IL, and thus the generated IL is coupled to that specific method and can't be re-used. See Change IL Emit invoke path to use function pointers and OpCodes.Calli #75357.Newobj
opcode to calling the internalGetUninitializedObject()
and then calling the constructor. SinceNewobj
, likeCall
, are based on method handles, this prevented re-use of emitted methods. Constructors can now be cached and re-used the same way as non-constructor methods. This also will make this issue obsolete: HaveConstructorInfo
useActivator.CreateInstance()
instead of emit for default constructors #78917.CallI
only applies to methods that can't be overridden sinceCallI
does not support vtables and adding vtable support before theCallI
would be too slow. The compatibility rules are nuanced, but basically methods are compatible if they have the same number of parameters and the same parameter\return types except that reference type parameters can all be consideredobject
which helps with the normalization.This PR also changes the heuristics of when emit is used:
Switch.System.Reflection.ForceInterpretedInvoke
is still supported. For CoreClr, we could remove the interpreted path pending findings -- the interpreted path is a large amount of code and also doesn't work with certain scenarios such as debugging in Visual Studio, so it would be a win to remove it.Performance notes:
CallI
and function pointers as well as refactoring. Some benchmarks will be slightly slower, and some slightly faster.Jit logging
BEFORE:
GetTotalMemory:733088
GetTotalAllocatedBytes:731192
GetTotalMemory after gc:278736
ELAPSED MS TO START CONSOLE APP: 68 (average)
AFTER:
GetTotalMemory:708648
GetTotalAllocatedBytes:706824
GetTotalMemory after gc:276120
ELAPSED MS TO START CONSOLE APP: 65 (average)
Invoke Benchmarks
fixes #90405
fixes #75357
fixes #78917