[Perf] Cache the default values for action arguments to avoid Activator.CreateInstance per argument.

Fixes #4470
This commit is contained in:
mnltejaswini 2016-04-27 18:15:01 -07:00
parent 5fb36ae406
commit 61a176e478
5 changed files with 118 additions and 29 deletions

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
object instance,
IDictionary<string, object> actionArguments)
{
var orderedArguments = PrepareArguments(actionArguments, actionMethodExecutor.MethodInfo.GetParameters());
var orderedArguments = PrepareArguments(actionArguments, actionMethodExecutor);
return ExecuteAsync(actionMethodExecutor, instance, orderedArguments);
}
@ -30,8 +30,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public static object[] PrepareArguments(
IDictionary<string, object> actionParameters,
ParameterInfo[] declaredParameterInfos)
ObjectMethodExecutor actionMethodExecutor)
{
var declaredParameterInfos = actionMethodExecutor.ActionParameters;
var count = declaredParameterInfos.Length;
if (count == 0)
{
@ -46,26 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
if (!actionParameters.TryGetValue(parameterInfo.Name, out value))
{
if (parameterInfo.HasDefaultValue)
{
value = parameterInfo.DefaultValue;
}
else
{
var defaultValueAttribute =
parameterInfo.GetCustomAttribute<DefaultValueAttribute>(inherit: false);
if (defaultValueAttribute?.Value == null)
{
value = parameterInfo.ParameterType.GetTypeInfo().IsValueType
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
else
{
value = defaultValueAttribute.Value;
}
}
value = actionMethodExecutor.GetDefaultValueForParameter(index);
}
arguments[index] = value;

View File

@ -569,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var arguments = ControllerActionExecutor.PrepareArguments(
_actionExecutingContext.ActionArguments,
actionMethodInfo.GetParameters());
_executor);
_logger.ActionMethodExecuting(_actionExecutingContext, arguments);

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
@ -14,6 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ObjectMethodExecutor
{
private object[] _parameterDefaultValues;
private ActionExecutorAsync _executorAsync;
private ActionExecutor _executor;
@ -31,7 +33,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static readonly MethodInfo _coerceMethod = ((MethodCallExpression)_coerceTaskExpression.Body).Method;
private ObjectMethodExecutor(MethodInfo methodInfo)
{
if (methodInfo == null)
@ -39,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
throw new ArgumentNullException(nameof(methodInfo));
}
MethodInfo = methodInfo;
ActionParameters = methodInfo.GetParameters();
}
private delegate Task<object> ActionExecutorAsync(object target, object[] parameters);
@ -49,6 +51,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public MethodInfo MethodInfo { get; }
public ParameterInfo[] ActionParameters { get; }
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
var executor = new ObjectMethodExecutor(methodInfo);
@ -67,6 +71,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return _executor(target, parameters);
}
public object GetDefaultValueForParameter(int index)
{
if (index < 0 || index > ActionParameters.Length - 1)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
EnsureParameterDefaultValues();
return _parameterDefaultValues[index];
}
private static ActionExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
// Parameters to executor
@ -238,5 +254,43 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var task = (Task<T>)taskAsObject;
return CastToObject<T>(task);
}
private void EnsureParameterDefaultValues()
{
if (_parameterDefaultValues == null)
{
var count = ActionParameters.Length;
_parameterDefaultValues = new object[count];
for (var i = 0; i < count; i++)
{
var parameterInfo = ActionParameters[i];
object defaultValue;
if (parameterInfo.HasDefaultValue)
{
defaultValue = parameterInfo.DefaultValue;
}
else
{
var defaultValueAttribute = parameterInfo
.GetCustomAttribute<DefaultValueAttribute>(inherit: false);
if (defaultValueAttribute?.Value == null)
{
defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
else
{
defaultValue = defaultValueAttribute.Value;
}
}
_parameterDefaultValues[i] = defaultValue;
}
}
}
}
}

View File

@ -102,10 +102,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
using (_logger.ViewComponentScope(context))
{
var method = context.ViewComponentDescriptor.MethodInfo;
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, method.GetParameters());
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
var arguments = ControllerActionExecutor.PrepareArguments(context.Arguments, methodExecutor);
_diagnosticSource.BeforeViewComponent(context, component);
_logger.ViewComponentExecuting(context, arguments);
@ -129,10 +129,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents
using (_logger.ViewComponentScope(context))
{
var method = context.ViewComponentDescriptor.MethodInfo;
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
var arguments = ControllerActionExecutor.PrepareArguments(
context.Arguments,
method.GetParameters());
var methodExecutor = _viewComponentInvokerCache.GetViewComponentMethodExecutor(context);
methodExecutor);
_diagnosticSource.BeforeViewComponent(context, component);
_logger.ViewComponentExecuting(context, arguments);

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Threading.Tasks;
@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var executor = GetExecutorForMethod("ValueMethod");
var result = executor.Execute(
_targetObject,
new object[] { 10 , 20 });
new object[] { 10, 20 });
Assert.Equal(30, (int)result);
}
@ -169,6 +170,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal(expectedException, ex.Message);
}
[Theory]
[InlineData("EchoWithDefaultAttributes", new object[] { "hello", true, 10 })]
[InlineData("EchoWithDefaultValues", new object[] { "hello", true, 20 })]
[InlineData("EchoWithDefaultValuesAndAttributes", new object[] { "hello", 20 })]
[InlineData("EchoWithNoDefaultAttributesAndValues", new object[] { null, 0, false, null })]
public void GetDefaultValueForParameters_ReturnsExpectedValues(string methodName, object[] expectedValues)
{
var executor = GetExecutorForMethod(methodName);
var defaultValues = new object[expectedValues.Length];
for (var index = 0; index < expectedValues.Length; index++)
{
defaultValues[index] = executor.GetDefaultValueForParameter(index);
}
Assert.Equal(expectedValues, defaultValues);
}
private ObjectMethodExecutor GetExecutorForMethod(string methodName)
{
var method = typeof(TestObject).GetMethod(methodName);
@ -239,6 +259,38 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return new TaskOfTDerivedType<int>(1);
}
public string EchoWithDefaultAttributes(
[DefaultValue("hello")] string input1,
[DefaultValue(true)] bool input2,
[DefaultValue(10)] int input3)
{
return input1;
}
public string EchoWithDefaultValues(
string input1 = "hello",
bool input2 = true,
int input3 = 20)
{
return input1;
}
public string EchoWithDefaultValuesAndAttributes(
[DefaultValue("Hi")] string input1 = "hello",
[DefaultValue(10)] int input3 = 20)
{
return input1;
}
public string EchoWithNoDefaultAttributesAndValues(
string input1,
int input2,
bool input3,
TestObject input4)
{
return input1;
}
public class TaskDerivedType : Task
{
public TaskDerivedType()