Skip to content

Commit

Permalink
Merge pull request #573 from stakx/interface-proxy-type-generation-sp…
Browse files Browse the repository at this point in the history
…eedup

Speed up interface proxy w/o target type generation significantly
  • Loading branch information
stakx authored Feb 4, 2021
2 parents c0fba5f + 7b6b193 commit 7eecd12
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -2787,6 +2787,15 @@ protected InheritanceInvocation(System.Type targetType, object proxy, Castle.Dyn
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
Original file line number Diff line number Diff line change
Expand Up @@ -2740,6 +2740,14 @@ protected InheritanceInvocation(System.Type targetType, object proxy, Castle.Dyn
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
Original file line number Diff line number Diff line change
Expand Up @@ -2740,6 +2740,14 @@ protected InheritanceInvocation(System.Type targetType, object proxy, Castle.Dyn
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
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
}
Original file line number Diff line number Diff line change
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 MethodGenerator GetMethodGenerator(MetaMethod method, ClassEm

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);
}

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 7eecd12

Please sign in to comment.