编译表达式比反射慢
Compiled Expression slower than Reflection
我有一个 PropertyInfo.SetValue 有一个动态集。意味着要设置的值是未知的。
我有一个这样的方法是从网上得到的。
private static Action<object, object> CreateSetAccess(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
var value = Expression.Parameter(typeof(object));
Expression<Action<object, object>> expr =
Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);
return expr.Compile();
}
这样做是创建一个表达式并对其进行编译,但对象会使用参数类型进行转换。
我是这样吃的
var method2 = CreateSetAccess(property.GetSetMethod());
method2(response, valueToSet);
发生的事情是,这似乎比 PropertyInfo.SetValue
慢
这是我的基准
var xpathNavigator = XmlHelper.CreateXPathDocument(serviceResponse).CreateNavigator();
foreach (var propertyInformation in propertyInformationSource)
{
// Gets the node using the NodePath provided in the Attribute
var attr = propertyInformation.Value;
var pathValue = xpathNavigator.SelectSingleNode(attr.NodePath);
if (pathValue == null)
continue;
object valueToSet = null;
var property = propertyInformation.Key;
if (propertyInformation.Value.ShouldDeserialize)
valueToSet = serializationHelper.Deserialize(property.PropertyType, pathValue.OuterXml, attr.CustomRoot);
else
valueToSet = Convert.ChangeType(pathValue.Value, property.PropertyType);
// this line is only added for the testing for it to be JITd
var method = CreateSetAccess(property.GetSetMethod());
method(response, valueToSet);
property.SetValue(response, valueToSet);
// end
TimeSpan fastSet, setValue;
const int COUNT = 100000;
var watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
var method2 = CreateSetAccess(property.GetSetMethod());
method2(response, valueToSet);
}
watch.Stop();
fastSet = watch.Elapsed; // result {00:00:08.8500760}
watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
property.SetValue(response, valueToSet);
}
watch.Stop();
setValue = watch.Elapsed; // result {00:00:00.0263953}
}
我想知道为什么会这样?我猜是因为我总是 creating 一个新表达式,但是我怎样才能让它 not 创建一个新对象并让它被缓存?
如果每次都编译一个新表达式更快,那么反射 API 只会在内部执行此操作。因此,它不是。此技术仅在您多次重复使用相同的编译代码时才有效。
so there is no way to make the expression adjust at runtime based on the methodinfo supplied?
反射就是这样做的,这就是它变慢的原因。保留已编译方法的缓存。例如在 Dictionary<MethodInfo, Action<object, object>>
中使用合适的 comparer.
在您的 class 中创建 Dictionary<MethodInfo, Action<object, object>> actions
字段。
您可以从 CreateSetAccess 中删除静态修饰符。然后在其中添加以下代码:
Action<object, object> action = null;
if(actions.TryGetValue(method, out action))
{
return action;
}
var obj = Expression.Parameter(typeof(object), "o");
var value = Expression.Parameter(typeof(object));
Expression<Action<object, object>> expr =
Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);
expr.Compile();
cache.Add(method, expr);
return expr;
}
我有一个 PropertyInfo.SetValue 有一个动态集。意味着要设置的值是未知的。
我有一个这样的方法是从网上得到的。
private static Action<object, object> CreateSetAccess(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
var value = Expression.Parameter(typeof(object));
Expression<Action<object, object>> expr =
Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);
return expr.Compile();
}
这样做是创建一个表达式并对其进行编译,但对象会使用参数类型进行转换。
我是这样吃的
var method2 = CreateSetAccess(property.GetSetMethod());
method2(response, valueToSet);
发生的事情是,这似乎比 PropertyInfo.SetValue
这是我的基准
var xpathNavigator = XmlHelper.CreateXPathDocument(serviceResponse).CreateNavigator();
foreach (var propertyInformation in propertyInformationSource)
{
// Gets the node using the NodePath provided in the Attribute
var attr = propertyInformation.Value;
var pathValue = xpathNavigator.SelectSingleNode(attr.NodePath);
if (pathValue == null)
continue;
object valueToSet = null;
var property = propertyInformation.Key;
if (propertyInformation.Value.ShouldDeserialize)
valueToSet = serializationHelper.Deserialize(property.PropertyType, pathValue.OuterXml, attr.CustomRoot);
else
valueToSet = Convert.ChangeType(pathValue.Value, property.PropertyType);
// this line is only added for the testing for it to be JITd
var method = CreateSetAccess(property.GetSetMethod());
method(response, valueToSet);
property.SetValue(response, valueToSet);
// end
TimeSpan fastSet, setValue;
const int COUNT = 100000;
var watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
var method2 = CreateSetAccess(property.GetSetMethod());
method2(response, valueToSet);
}
watch.Stop();
fastSet = watch.Elapsed; // result {00:00:08.8500760}
watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
property.SetValue(response, valueToSet);
}
watch.Stop();
setValue = watch.Elapsed; // result {00:00:00.0263953}
}
我想知道为什么会这样?我猜是因为我总是 creating 一个新表达式,但是我怎样才能让它 not 创建一个新对象并让它被缓存?
如果每次都编译一个新表达式更快,那么反射 API 只会在内部执行此操作。因此,它不是。此技术仅在您多次重复使用相同的编译代码时才有效。
so there is no way to make the expression adjust at runtime based on the methodinfo supplied?
反射就是这样做的,这就是它变慢的原因。保留已编译方法的缓存。例如在 Dictionary<MethodInfo, Action<object, object>>
中使用合适的 comparer.
在您的 class 中创建 Dictionary<MethodInfo, Action<object, object>> actions
字段。
您可以从 CreateSetAccess 中删除静态修饰符。然后在其中添加以下代码:
Action<object, object> action = null;
if(actions.TryGetValue(method, out action))
{
return action;
}
var obj = Expression.Parameter(typeof(object), "o");
var value = Expression.Parameter(typeof(object));
Expression<Action<object, object>> expr =
Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);
expr.Compile();
cache.Add(method, expr);
return expr;
}