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

Speed up interface proxy w/o target type generation significantly #573

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
Enhancements:
- .NET Standard 2.0 and 2.1 support (@lg2de, #485)
- Non-intercepted methods on a class proxy with target are now forwarded to the target (@stakx, #571)
- Significant performance improvements with proxy type generation for interface proxies without target. (Up until now, DynamicProxy generated a separate `IInvocation` implementation type for every single proxied method – it is now able to reuse a single predefined type in many cases, thereby reducing the total amount of dynamic type generation.) (@stakx, #573)

Bugfixes:
- Proxying certain `[Serializable]` classes produces proxy types that fail PEVerify test (@stakx, #367)
Expand Down
9 changes: 9 additions & 0 deletions ref/Castle.Core-net45.cs
Expand Up @@ -2787,6 +2787,15 @@ public abstract class InheritanceInvocation : Castle.DynamicProxy.AbstractInvoca
public override System.Type TargetType { get; }
protected abstract override void InvokeMethodOnTarget() { }
}
[System.Serializable]
public sealed class InterfaceMethodWithoutTargetInvocation : Castle.DynamicProxy.AbstractInvocation
{
public InterfaceMethodWithoutTargetInvocation(object target, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { }
public override object InvocationTarget { get; }
public override System.Reflection.MethodInfo MethodInvocationTarget { get; }
public override System.Type TargetType { get; }
protected override void InvokeMethodOnTarget() { }
}
public static class TypeUtil
{
public static System.Type[] GetAllInterfaces(this System.Type type) { }
Expand Down
8 changes: 8 additions & 0 deletions ref/Castle.Core-netstandard2.0.cs
Expand Up @@ -2740,6 +2740,14 @@ public abstract class InheritanceInvocation : Castle.DynamicProxy.AbstractInvoca
public override System.Type TargetType { get; }
protected abstract override void InvokeMethodOnTarget() { }
}
public sealed class InterfaceMethodWithoutTargetInvocation : Castle.DynamicProxy.AbstractInvocation
{
public InterfaceMethodWithoutTargetInvocation(object target, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { }
public override object InvocationTarget { get; }
public override System.Reflection.MethodInfo MethodInvocationTarget { get; }
public override System.Type TargetType { get; }
protected override void InvokeMethodOnTarget() { }
}
public static class TypeUtil
{
public static System.Type[] GetAllInterfaces(this System.Type type) { }
Expand Down
8 changes: 8 additions & 0 deletions ref/Castle.Core-netstandard2.1.cs
Expand Up @@ -2740,6 +2740,14 @@ public abstract class InheritanceInvocation : Castle.DynamicProxy.AbstractInvoca
public override System.Type TargetType { get; }
protected abstract override void InvokeMethodOnTarget() { }
}
public sealed class InterfaceMethodWithoutTargetInvocation : Castle.DynamicProxy.AbstractInvocation
{
public InterfaceMethodWithoutTargetInvocation(object target, object proxy, Castle.DynamicProxy.IInterceptor[] interceptors, System.Reflection.MethodInfo proxiedMethod, object[] arguments) { }
public override object InvocationTarget { get; }
public override System.Reflection.MethodInfo MethodInvocationTarget { get; }
public override System.Type TargetType { get; }
protected override void InvokeMethodOnTarget() { }
}
public static class TypeUtil
{
public static System.Type[] GetAllInterfaces(this System.Type type) { }
Expand Down
@@ -0,0 +1,75 @@
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy.Tests
{
using System;

using Castle.DynamicProxy.Internal;

using NUnit.Framework;

/// <summary>
/// This fixture checks which <see cref="IInvocation"/> types get used for proxied methods.
/// Usually, DynamicProxy generates a separate implementation type per proxied method, but
/// in some cases, it can reuse predefined implementation types. Because this is beneficial
/// for runtime performance (as it reduces the amount of dynamic type generation performed),
/// we want to ensure that those predefined types do in fact get picked when they should be.
/// </summary>
[TestFixture]
public class InvocationTypeReuseTestCase : BasePEVerifyTestCase
{
[Test]
public void Non_generic_method_of_interface_proxy_without_target__uses__InterfaceMethodWithoutTargetInvocation()
{
var recorder = new InvocationTypeRecorder();

var proxy = generator.CreateInterfaceProxyWithoutTarget<IWithNonGenericMethod>(recorder);
proxy.Method();

Assert.AreEqual(typeof(InterfaceMethodWithoutTargetInvocation), recorder.InvocationType);
}

[Test]
public void Generic_method_of_interface_proxy_without_target__uses__InterfaceMethodWithoutTargetInvocation()
{
var recorder = new InvocationTypeRecorder();

var proxy = generator.CreateInterfaceProxyWithoutTarget<IWithGenericMethod>(recorder);
proxy.Method(42);

Assert.AreEqual(typeof(InterfaceMethodWithoutTargetInvocation), recorder.InvocationType);
}

public interface IWithNonGenericMethod
{
void Method();
}

public interface IWithGenericMethod
{
void Method<T>(T arg);
}

private sealed class InvocationTypeRecorder : IInterceptor
{
public Type InvocationType { get; private set; }

public void Intercept(IInvocation invocation)
{
InvocationType = invocation.GetType();
}
}
}
}
Expand Up @@ -20,6 +20,7 @@ namespace Castle.DynamicProxy.Contributors

using Castle.DynamicProxy.Generators;
using Castle.DynamicProxy.Generators.Emitters;
using Castle.DynamicProxy.Internal;

internal class InterfaceProxyWithoutTargetContributor : CompositeTypeContributor
{
Expand Down Expand Up @@ -60,6 +61,15 @@ protected override IEnumerable<MembersCollector> GetCollectors()

private Type GetInvocationType(MetaMethod method, ClassEmitter emitter)
{
var methodInfo = method.Method;

if (canChangeTarget == false && methodInfo.IsAbstract)
{
// We do not need to generate a custom invocation type because no custom implementation
// for `InvokeMethodOnTarget` will be needed (proceeding to target isn't possible here):
return typeof(InterfaceMethodWithoutTargetInvocation);
jonorossi marked this conversation as resolved.
Show resolved Hide resolved
}

var scope = emitter.ModuleScope;
Type[] invocationInterfaces;
if (canChangeTarget)
Expand All @@ -70,14 +80,14 @@ private Type GetInvocationType(MetaMethod method, ClassEmitter emitter)
{
invocationInterfaces = new[] { typeof(IInvocation) };
}
var key = new CacheKey(method.Method, CompositionInvocationTypeGenerator.BaseType, invocationInterfaces, null);
var key = new CacheKey(methodInfo, CompositionInvocationTypeGenerator.BaseType, invocationInterfaces, null);

// no locking required as we're already within a lock

return scope.TypeCache.GetOrAddWithoutTakingLock(key, _ =>
new CompositionInvocationTypeGenerator(method.Method.DeclaringType,
new CompositionInvocationTypeGenerator(methodInfo.DeclaringType,
method,
method.Method,
methodInfo,
canChangeTarget,
null)
.Generate(emitter, namingScope)
Expand Down
Expand Up @@ -73,21 +73,23 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C
{
var invocationType = invocation;

Trace.Assert(MethodToOverride.IsGenericMethod == invocationType.IsGenericTypeDefinition);
var genericArguments = Type.EmptyTypes;

var constructor = invocation.GetConstructors()[0];

IExpression proxiedMethodTokenExpression;
if (MethodToOverride.IsGenericMethod)
{
// bind generic method arguments to invocation's type arguments
genericArguments = emitter.MethodBuilder.GetGenericArguments();
invocationType = invocationType.MakeGenericType(genericArguments);
constructor = TypeBuilder.GetConstructor(invocationType, constructor);

// Not in the cache: generic method
genericArguments = emitter.MethodBuilder.GetGenericArguments();
proxiedMethodTokenExpression = new MethodTokenExpression(MethodToOverride.MakeGenericMethod(genericArguments));

if (invocationType.IsGenericTypeDefinition)
{
// bind generic method arguments to invocation's type arguments
invocationType = invocationType.MakeGenericType(genericArguments);
constructor = TypeBuilder.GetConstructor(invocationType, constructor);
}
}
else
{
Expand Down
@@ -0,0 +1,61 @@
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy.Internal
{
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;

#if FEATURE_SERIALIZATION
[Serializable]
#endif
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class InterfaceMethodWithoutTargetInvocation : AbstractInvocation
{
public InterfaceMethodWithoutTargetInvocation(object target, object proxy, IInterceptor[] interceptors, MethodInfo proxiedMethod, object[] arguments)
: base(proxy, interceptors, proxiedMethod, arguments)
{
// This invocation type is suitable for interface method invocations that cannot proceed
// to a target, i.e. where `InvokeMethodOnTarget` will always throw:

Debug.Assert(target == null, $"{nameof(InterfaceMethodWithoutTargetInvocation)} does not support targets.");
Debug.Assert(proxiedMethod.IsAbstract, $"{nameof(InterfaceMethodWithoutTargetInvocation)} does not support non-abstract methods.");

// Why this restriction? Because it greatly benefits proxy type generation performance.
//
// For invocations that can proceed to a target, `InvokeMethodOnTarget`'s implementation
// depends on the target method's signature. Because of this, DynamicProxy needs to
// dynamically generate a separate invocation type per such method. Type generation is
// always expensive... that is, slow.
//
// However, if it is known that `InvokeMethodOnTarget` won't forward, but throw,
// no custom (dynamically generated) invocation type is needed at all, and we can use
// this unspecific invocation type instead.
}

// The next three properties mimick the behavior seen with an interface proxy without target.
// (This is why this type's name starts with `Interface`.) A similar type could be written
// for class proxies without target, but the values returned here would be different.

public override object InvocationTarget => null;

public override MethodInfo MethodInvocationTarget => null;

public override Type TargetType => null;

protected override void InvokeMethodOnTarget() => ThrowOnNoTarget();
}
}