Skip to content

Commit

Permalink
Merge pull request #1779 from snakefoot/LateBoundMethodInfo
Browse files Browse the repository at this point in the history
Improve performance of filters (2-3 x faster)
  • Loading branch information
304NotModified committed Nov 23, 2016
2 parents 075f490 + b4d0a03 commit 62abf65
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 12 deletions.
39 changes: 28 additions & 11 deletions src/NLog/Conditions/ConditionMethodExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,29 @@ public ConditionMethodExpression(string conditionMethodName, MethodInfo methodIn
InternalLogger.Error(message);
throw new ConditionParseException(message);
}

this.lateBoundMethod = Internal.ReflectionHelpers.CreateLateBoundMethod(MethodInfo);
if (formalParameters.Length > MethodParameters.Count)
{
this.lateBoundMethodDefaultParameters = new object[formalParameters.Length - MethodParameters.Count];
for (int i = MethodParameters.Count; i < formalParameters.Length; ++i)
{
ParameterInfo param = formalParameters[i];
this.lateBoundMethodDefaultParameters[i - MethodParameters.Count] = param.DefaultValue;
}
}
else
{
this.lateBoundMethodDefaultParameters = null;
}
}

/// <summary>
/// Gets the method info.
/// </summary>
public MethodInfo MethodInfo { get; private set; }
private readonly Internal.ReflectionHelpers.LateBoundMethod lateBoundMethod;
private readonly object[] lateBoundMethodDefaultParameters;

/// <summary>
/// Gets the method parameters.
Expand Down Expand Up @@ -159,8 +176,9 @@ public override string ToString()
protected override object EvaluateNode(LogEventInfo context)
{
int parameterOffset = this.acceptsLogEvent ? 1 : 0;
int parameterDefaults = this.lateBoundMethodDefaultParameters != null ? this.lateBoundMethodDefaultParameters.Length : 0;

var callParameters = new object[this.MethodParameters.Count + parameterOffset];
var callParameters = new object[this.MethodParameters.Count + parameterOffset + parameterDefaults];

//Memory profiling pointed out that using a foreach-loop was allocating
//an Enumerator. Switching to a for-loop avoids the memory allocation.
Expand All @@ -175,16 +193,15 @@ protected override object EvaluateNode(LogEventInfo context)
callParameters[0] = context;
}

return this.MethodInfo.DeclaringType.InvokeMember(
MethodInfo.Name,
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public | BindingFlags.OptionalParamBinding,
null,
null,
callParameters
#if !SILVERLIGHT
, CultureInfo.InvariantCulture
#endif
);
if (this.lateBoundMethodDefaultParameters != null)
{
for (int i = this.lateBoundMethodDefaultParameters.Length - 1; i >= 0; --i)
{
callParameters[callParameters.Length - i - 1] = this.lateBoundMethodDefaultParameters[i];
}
}

return this.lateBoundMethod(null, callParameters); // Static-method so object-instance = null
}
}
}
68 changes: 67 additions & 1 deletion src/NLog/Internal/ReflectionHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ namespace NLog.Internal
{
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using NLog.Common;

/// <summary>
Expand Down Expand Up @@ -92,6 +92,72 @@ public static bool IsStaticClass(this Type type)
{
return type.IsClass && type.IsAbstract && type.IsSealed;
}

/// <summary>
/// Optimized delegate for calling MethodInfo
/// </summary>
/// <param name="target">Object instance, use null for static methods.</param>
/// <param name="arguments">Complete list of parameters that matches the method, including optional/default parameters.</param>
/// <returns></returns>
public delegate object LateBoundMethod(object target, object[] arguments);

/// <summary>
/// Creates an optimized delegate for calling the MethodInfo using Expression-Trees
/// </summary>
/// <param name="methodInfo">Method to optimize</param>
/// <returns>Optimized delegate for invoking the MethodInfo</returns>
public static LateBoundMethod CreateLateBoundMethod(MethodInfo methodInfo)
{
// parameters to execute
var instanceParameter = Expression.Parameter(typeof(object), "instance");
var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");

// build parameter list
var parameterExpressions = new List<Expression>();
var paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
// (Ti)parameters[i]
var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));

Type parameterType = paramInfos[i].ParameterType;
if (parameterType.IsByRef)
parameterType = parameterType.GetElementType();

var valueCast = Expression.Convert(valueObj, parameterType);

parameterExpressions.Add(valueCast);
}

// non-instance for static method, or ((TInstance)instance)
var instanceCast = methodInfo.IsStatic ? null :
Expression.Convert(instanceParameter, methodInfo.ReflectedType);

// static invoke or ((TInstance)instance).Method
var methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);

// ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
if (methodCall.Type == typeof(void))
{
var lambda = Expression.Lambda<Action<object, object[]>>(
methodCall, instanceParameter, parametersParameter);

Action<object, object[]> execute = lambda.Compile();
return (instance, parameters) =>
{
execute(instance, parameters);
return null; // There is no return-type, so we return null-object
};
}
else
{
var castMethodCall = Expression.Convert(methodCall, typeof(object));
var lambda = Expression.Lambda<LateBoundMethod>(
castMethodCall, instanceParameter, parametersParameter);

return lambda.Compile();
}
}
}

}

0 comments on commit 62abf65

Please sign in to comment.