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

Use dynamically compiled factory instead of Activator.CreateInstance in TypedClientBuilder #14615

Merged
merged 7 commits into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/SignalR/perf/Microbenchmarks/TypedClientBuilderBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.SignalR.Internal;

namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
{
public class TypedClientBuilderBenchmark
{
private static readonly IClientProxy Dummy = new DummyProxy();

[Benchmark]
public ITestClient Build()
{
return TypedClientBuilder<ITestClient>.Build(Dummy);
}

public interface ITestClient { }

private class DummyProxy : IClientProxy
{
public Task SendCoreAsync(string method, object[] args, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
}
}
}
55 changes: 35 additions & 20 deletions src/SignalR/server/Core/src/Internal/TypedClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ internal static class TypedClientBuilder<T>

private static readonly PropertyInfo CancellationTokenNoneProperty = typeof(CancellationToken).GetProperty("None", BindingFlags.Public | BindingFlags.Static);

private static readonly ConstructorInfo ObjectConstructor = typeof(object).GetConstructors().Single();

private static readonly Type[] ParameterTypes = new Type[] { typeof(IClientProxy) };

public static T Build(IClientProxy proxy)
{
return _builder.Value(proxy);
Expand All @@ -40,20 +44,24 @@ private static Func<IClientProxy, T> GenerateClientBuilder()
var moduleBuilder = assemblyBuilder.DefineDynamicModule(ClientModuleName);
var clientType = GenerateInterfaceImplementation(moduleBuilder);

return proxy => (T)Activator.CreateInstance(clientType, proxy);
var factoryMethod = clientType.GetMethod(nameof(Build), BindingFlags.Public | BindingFlags.Static);
return (Func<IClientProxy, T>)factoryMethod.CreateDelegate(typeof(Func<IClientProxy, T>));
}

private static Type GenerateInterfaceImplementation(ModuleBuilder moduleBuilder)
{
var type = moduleBuilder.DefineType(
ClientModuleName + "." + typeof(T).Name + "Impl",
TypeAttributes.Public,
typeof(Object),
new[] { typeof(T) });
var name = ClientModuleName + "." + typeof(T).Name + "Impl";

var type = moduleBuilder.DefineType(name, TypeAttributes.Public, typeof(object), new[] { typeof(T) });

var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private | FieldAttributes.InitOnly);

var proxyField = type.DefineField("_proxy", typeof(IClientProxy), FieldAttributes.Private);
var ctor = BuildConstructor(type, proxyField);

BuildConstructor(type, proxyField);
// Because a constructor doesn't return anything, it can't be wrapped in a
// delegate directly, so we emit a factory method that just takes the IClientProxy,
// invokes the constructor (using newobj) and returns the new instance of type T.
BuildFactoryMethod(type, ctor);

foreach (var method in GetAllInterfaceMethods(typeof(T)))
{
Expand All @@ -79,27 +87,23 @@ private static IEnumerable<MethodInfo> GetAllInterfaceMethods(Type interfaceType
}
}

private static void BuildConstructor(TypeBuilder type, FieldInfo proxyField)
private static ConstructorInfo BuildConstructor(TypeBuilder type, FieldInfo proxyField)
{
var method = type.DefineMethod(".ctor", System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.HideBySig);
var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ParameterTypes);

var ctor = typeof(object).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null, new Type[] { }, null);

method.SetReturnType(typeof(void));
method.SetParameters(typeof(IClientProxy));

var generator = method.GetILGenerator();
var generator = ctor.GetILGenerator();

// Call object constructor
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Call, ctor);
generator.Emit(OpCodes.Call, ObjectConstructor);

// Assign constructor argument to the proxyField
generator.Emit(OpCodes.Ldarg_0); // type
generator.Emit(OpCodes.Ldarg_1); // type proxyfield
generator.Emit(OpCodes.Stfld, proxyField); // type.proxyField = proxyField
generator.Emit(OpCodes.Ret);

return ctor;
}

private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo, FieldInfo proxyField)
Expand Down Expand Up @@ -187,6 +191,17 @@ private static void BuildMethod(TypeBuilder type, MethodInfo interfaceMethodInfo
generator.Emit(OpCodes.Ret); // Return the Task returned by 'invokeMethod'
}

private static void BuildFactoryMethod(TypeBuilder type, ConstructorInfo ctor)
khellang marked this conversation as resolved.
Show resolved Hide resolved
{
var method = type.DefineMethod(nameof(Build), MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(T), ParameterTypes);

var generator = method.GetILGenerator();

generator.Emit(OpCodes.Ldarg_0); // Load the IClientProxy argument onto the stack
generator.Emit(OpCodes.Newobj, ctor); // Call the generated constructor with the proxy
generator.Emit(OpCodes.Ret); // Return the typed client
}

private static void VerifyInterface(Type interfaceType)
{
if (!interfaceType.IsInterface)
Expand All @@ -206,7 +221,7 @@ private static void VerifyInterface(Type interfaceType)

foreach (var method in interfaceType.GetMethods())
{
VerifyMethod(interfaceType, method);
VerifyMethod(method);
}

foreach (var parent in interfaceType.GetInterfaces())
Expand All @@ -215,7 +230,7 @@ private static void VerifyInterface(Type interfaceType)
}
}

private static void VerifyMethod(Type interfaceType, MethodInfo interfaceMethod)
private static void VerifyMethod(MethodInfo interfaceMethod)
{
if (interfaceMethod.ReturnType != typeof(Task))
{
Expand Down