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

Optimize RPC target reflection #596

Merged
merged 1 commit into from
Oct 29, 2020
Merged

Conversation

AArnott
Copy link
Member

@AArnott AArnott commented Oct 29, 2020

Cache the results of reflection based on the type and options so subsequent connections can be made faster and with less GC pressure.
Also move all the RPC target handling from JsonRpc to its own class.

This makes a short-lived "use once" JsonRpc connection 18% faster and cuts out some large allocations.

Related to #575

@AArnott AArnott added this to the v2.7 milestone Oct 29, 2020
@AArnott AArnott self-assigned this Oct 29, 2020
Copy link
Member

@BertanAygun BertanAygun left a comment

Choose a reason for hiding this comment

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

I assumed most of the new code in RpcTargetInfo.cs is actually moved from JsonRpc.cs and didn't review it thoroughly. Let me know if there is a section that's new code.

internal static MethodNameMap GetMethodNameMap(TypeInfo type)
{
MethodNameMap? map;
lock (MethodNameMaps)
Copy link
Member

Choose a reason for hiding this comment

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

I assume this means gains by reduced GC pressure outweighs the overhead of the lock?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh yes, very much so. 18% faster, remember. 😏
And ya, the reflection that we skip here is expensive in terms of GC pressure. Almost every API we call creates arrays, even if we just wanted one object. Even GetCustomAttribute calls another API that returns an array before returning a single element from it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also note, these are all optimistic locking. We take it just long enough to peek, then we release the lock to do the work. As a result, this lock is not held for very long at all => minimal lock contention.

/// </param>
internal JsonRpcMethodAttribute? GetJsonRpcMethodAttribute(string methodName, ReadOnlySpan<ParameterInfo> parameters)
{
if (this.targetRequestMethodToClrMethodMap.TryGetValue(methodName, out List<MethodSignatureAndTarget>? existingList))
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 need locks here or can a local rpc target object be added only before calling these?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oooh. good call. Accessing this dictionary without a lock would be dangerous. I'm adding the lock now.

{
private const string ImpliedMethodNameAsyncSuffix = "Async";
private static readonly Dictionary<TypeInfo, MethodNameMap> MethodNameMaps = new Dictionary<TypeInfo, MethodNameMap>();
private static readonly Dictionary<(TypeInfo Type, bool AllowNonPublicInvocation, bool UseSingleObjectParameterDeserialization), Dictionary<string, List<MethodSignature>>> RequestMethodToClrMethodMap = new Dictionary<(TypeInfo Type, bool AllowNonPublicInvocation, bool UseSingleObjectParameterDeserialization), Dictionary<string, List<MethodSignature>>>();
Copy link
Member

Choose a reason for hiding this comment

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

Dictionary<(TypeInfo Type, bool AllowNonPublicInvocation, bool UseSingleObjectParameterDeserialization), Dictionary<string, List>> [](start = 214, length = 147)

new()?

Copy link
Member Author

Choose a reason for hiding this comment

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

Love it. Just as soon as C# 9 hits GA. 😉

/// <summary>
/// A collection of target objects and their map of clr method to <see cref="JsonRpcMethodAttribute"/> values.
/// </summary>
private readonly Dictionary<string, List<MethodSignatureAndTarget>> targetRequestMethodToClrMethodMap = new Dictionary<string, List<MethodSignatureAndTarget>>(StringComparer.Ordinal);
Copy link
Member

Choose a reason for hiding this comment

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

Dictionary<string, List> [](start = 116, length = 50)

new(StringComparer.Ordinal)

Copy link
Member Author

Choose a reason for hiding this comment

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

When C# 9 is GA, certainly. :)

{
if (exceptions is null)
{
exceptions = new List<Exception>();
Copy link
Member

Choose a reason for hiding this comment

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

exceptions [](start = 28, length = 10)

Can be simplified to exceptions ??= new List<Exception>();

Copy link
Member Author

Choose a reason for hiding this comment

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

And it works in C# 8. Nice. :)

Copy link
Member

Choose a reason for hiding this comment

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

Cache the results of reflection based on the type and options so subsequent connections can be made faster and with less GC pressure.
Also move all the RPC target handling from JsonRpc to its own class.
@AArnott AArnott merged commit 58528e0 into microsoft:master Oct 29, 2020
@AArnott AArnott deleted the targetObjectPerf branch October 29, 2020 22:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants