C# 缓存编译的表达式树
如何高效地缓存从表达式树编译的方法C# 缓存编译的表达式树,c#,expression-trees,C#,Expression Trees,如何高效地缓存从表达式树编译的方法 public void SomeToStringCalls() { ToString(i => (i + 1).ToString(), 1); ToString(i => (i + 1).ToString(), 2); ToString(i => (i + 2).ToString(), 3); ToString(i => (i + 2).ToString(), 4); } private string
public void SomeToStringCalls()
{
ToString(i => (i + 1).ToString(), 1);
ToString(i => (i + 1).ToString(), 2);
ToString(i => (i + 2).ToString(), 3);
ToString(i => (i + 2).ToString(), 4);
}
private string ToString<T>(Expression<Func<T, string>> expression, T input)
{
var method = expression.Compile();
return method.Invoke(input);
}
public void SomeToStringCalls()
{
ToString(i=>(i+1).ToString(),1);
ToString(i=>(i+1).ToString(),2);
ToString(i=>(i+2).ToString(),3);
ToString(i=>(i+2).ToString(),4);
}
私有字符串到字符串(表达式,T输入)
{
var method=expression.Compile();
返回方法。调用(输入);
}
在上面,每个调用都将重新编译每个表达式,即使有些表达式是相同的。我无法使用
Dictionary()
缓存表达式中已编译的方法,因为等于
将失败 无法使用字典的原因是表达式GetHashCode
不够智能,无法检测“相等”表达式。我不确定,但很可能Expression.GetHashCode
返回表达式的内存地址
为了解决这个问题,您可以引入更“智能”的哈希计算。让我们考虑平等的表达方式。这条路很滑,但如果你愿意承担责任,确保:
没有两个不同的表达式具有相同的哈希代码
具有相同主体的两个表达式具有相同的哈希代码
你可以实现你想要的
这是我在pastebin为你准备的简单的。这不是行业实力(评论中有一些改进的暗示),但它清楚地证明了该方法的可行性
在进一步阐述之前要考虑的两件事:不正确的散列函数可能会导致相当棘手的错误。因此,三思而后行,编写大量的单元测试并加以验证:)
您所描述的问题非常严重,因为计算两个表达式的语义相等性至少与编译表达式一样昂贵。为了说明这一点,提供了一个指向表达式相等实现的链接。此实现并不完美,例如:
MethodA() { MethodB(); }
MethodB() { ... }
在上面的例子中,<代码>方法> <代码>和>代码>方法B <代码>等同于它们做相同的事情,并且您最可能想把它们看作等价的。例如,在启用编译器优化的情况下在C#中构建此函数将用
MethodA
调用替换MethodB
调用。在比较代码时有很多问题,这是一个正在进行的研究主题
您应该考虑一个设计,在表达式中,如果您发现编译表达式是应用程序中的瓶颈,那么某种类型的引用引用表达式来标识它。strong>当您确定相等性时,您可能已经编译了它。
为了评论J0HN的答案,它比较了主体的哈希代码和参数,这绝不是一个可靠的解决方案,因为它没有对表达式树进行深入的计算
另外,请查看评论中发布的内容。在集中缓存中缓存表达式树的问题有:
您将需要全面的相等比较和哈希算法
您需要自己实现这些算法,因为标准表达式类型不提供现成的算法
进行全面的平等性比较的成本很高,但使用廉价的哈希函数可以在一定程度上降低成本。此外,由于表达式树是不可变的,因此在第一次计算哈希代码后,可以缓存该哈希代码。这可能会减少一些查找时间,但每次使用新创建的表达式作为键检查缓存时(我想,大多数情况下都是这样),您至少需要对新表达式进行哈希运算
选项1:本地/静态缓存
理想的解决方案可以避免所有这些开销。如果这是可行的(即,如果这些表达式不是动态组合的),那么最好的办法就是将表达式树缓存在其声明站点附近。您应该能够在静态字段中存储其中的大部分(如果不是全部):
private static readonly Expression<Func<int, string>> _addOne =
i => (i + 1).ToString();
private static readonly Expression<Func<int, string>> _addTwo =
i => (i + 2).ToString();
public void SomeToStringCalls()
{
ToString(_addOne, 1);
ToString(_addOne, 2);
ToString(_addTwo, 3);
ToString(_addTwo, 4);
}
这并不完美,但应该会给你带来不错的效果:
它深入分析并包含树中的每个表达式
至少,每个子表达式的节点类型
都包含在散列中。一个明显(但可能代价高昂)的改进是还包括类型
;如果发现碰撞太多,请尝试
包含表达式中引用的成员和类型
包含树中显示的常量值
它不需要堆分配来运行,但代价是不可重入(因为您只分析顶级表达式,这很好)。您可以在多个线程上同时运行它
由于实际上没有为任何表达式类型重写GetHashCode()
,因此哈希函数的缺陷不会泄漏出来并影响外部代码。这给了我们一定程度的自由来弯曲散列函数的规则
你的平等比较需要更全面。虽然散列函数实际上是一种用于最小化候选集的“估计”,但相等性比较执行实际匹配,需要做到完美。您当然可以使用我建议的哈希解决方案作为解决问题的模板,但请记住,您必须对表达式的所有属性执行详尽的比较。需要记住的一件事是,您可能不想比较树中ParameterExpression
节点的名称,但需要维护正在比较的两个树中的参数/变量的映射,以确保它们在其父表达式树的上下文中表示“相同”的值
除了忽略参数/变量名之外,不要费心尝试解析“语义e”
internal static class ExpressionHasher
{
private const int NullHashCode = 0x61E04917;
[ThreadStatic]
private static HashVisitor _visitor;
private static HashVisitor Visitor
{
get
{
if (_visitor == null)
_visitor = new HashVisitor();
return _visitor;
}
}
public static int GetHashCode(Expression e)
{
if (e == null)
return NullHashCode;
var visitor = Visitor;
visitor.Reset();
visitor.Visit(e);
return visitor.Hash;
}
private sealed class HashVisitor : ExpressionVisitor
{
private int _hash;
internal int Hash
{
get { return _hash; }
}
internal void Reset()
{
_hash = 0;
}
private void UpdateHash(int value)
{
_hash = (_hash * 397) ^ value;
}
private void UpdateHash(object component)
{
int componentHash;
if (component == null)
{
componentHash = NullHashCode;
}
else
{
var member = component as MemberInfo;
if (member != null)
{
componentHash = member.Name.GetHashCode();
var declaringType = member.DeclaringType;
if (declaringType != null && declaringType.AssemblyQualifiedName != null)
componentHash = (componentHash * 397) ^ declaringType.AssemblyQualifiedName.GetHashCode();
}
else
{
componentHash = component.GetHashCode();
}
}
_hash = (_hash * 397) ^ componentHash;
}
public override Expression Visit(Expression node)
{
UpdateHash((int)node.NodeType);
return base.Visit(node);
}
protected override Expression VisitConstant(ConstantExpression node)
{
UpdateHash(node.Value);
return base.VisitConstant(node);
}
protected override Expression VisitMember(MemberExpression node)
{
UpdateHash(node.Member);
return base.VisitMember(node);
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
UpdateHash(node.Member);
return base.VisitMemberAssignment(node);
}
protected override MemberBinding VisitMemberBinding(MemberBinding node)
{
UpdateHash((int)node.BindingType);
UpdateHash(node.Member);
return base.VisitMemberBinding(node);
}
protected override MemberListBinding VisitMemberListBinding(MemberListBinding node)
{
UpdateHash((int)node.BindingType);
UpdateHash(node.Member);
return base.VisitMemberListBinding(node);
}
protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
{
UpdateHash((int)node.BindingType);
UpdateHash(node.Member);
return base.VisitMemberMemberBinding(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
UpdateHash(node.Method);
return base.VisitMethodCall(node);
}
protected override Expression VisitNew(NewExpression node)
{
UpdateHash(node.Constructor);
return base.VisitNew(node);
}
protected override Expression VisitNewArray(NewArrayExpression node)
{
UpdateHash(node.Type);
return base.VisitNewArray(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
UpdateHash(node.Type);
return base.VisitParameter(node);
}
protected override Expression VisitTypeBinary(TypeBinaryExpression node)
{
UpdateHash(node.Type);
return base.VisitTypeBinary(node);
}
}
}
private static object ExtractValue(Expression expression, object[] input, ReadOnlyCollection<ParameterExpression> parameters)
{
if (expression == null)
{
return null;
}
var ce = expression as ConstantExpression;
if (ce != null)
{
return ce.Value;
}
var pe = expression as ParameterExpression;
if (pe != null)
{
return input[parameters.IndexOf(pe)];
}
var ma = expression as MemberExpression;
if (ma != null)
{
var se = ma.Expression;
object val = null;
if (se != null)
{
val = ExtractValue(se, input, parameters);
}
var fi = ma.Member as FieldInfo;
if (fi != null)
{
return fi.GetValue(val);
}
else
{
var pi = ma.Member as PropertyInfo;
if (pi != null)
{
return pi.GetValue(val);
}
}
}
var mce = expression as MethodCallExpression;
if (mce != null)
{
return mce.Method.Invoke(ExtractValue(mce.Object, input, parameters), mce.Arguments.Select(a => ExtractValue(a, input, parameters)).ToArray());
}
var sbe = expression as BinaryExpression;
if (sbe != null)
{
var left = ExtractValue(sbe.Left, input, parameters);
var right = ExtractValue(sbe.Right, input, parameters);
// TODO: check for other types and operands
if (sbe.NodeType == ExpressionType.Add)
{
if (left is int && right is int)
{
return (int) left + (int) right;
}
}
throw new NotImplementedException();
}
var le = expression as LambdaExpression;
if (le != null)
{
return ExtractValue(le.Body, input, le.Parameters);
}
// TODO: Check for other expression types
var dynamicInvoke = Expression.Lambda(expression).Compile().DynamicInvoke();
return dynamicInvoke;
}
private static string ToString<T>(Expression<Func<T, string>> expression, T input)
{
var sw = Stopwatch.StartNew();
var method = expression.Compile();
var invoke = method.Invoke(input);
sw.Stop();
Console.WriteLine("Compile + Invoke: {0}, {1} ms", invoke, sw.Elapsed.TotalMilliseconds);
sw.Restart();
var r2 = ExtractValue(expression, new object[] {input}, null);
sw.Stop();
Console.WriteLine("ExtractValue: {0}, {1} ms", r2, sw.Elapsed.TotalMilliseconds);
return invoke;
}
public static Dictionary<string, object> cache
= new Dictionary<string, object>();
public static string ToString<T>(
Expression<Func<T, string>> expression,
T input)
{
string key = typeof(T).FullName + ":" + expression.ToString();
object o; cache.TryGetValue(key, out o);
Func<T, string> method = (Func<T, string>)o;
if (method == null)
{
method = expression.Compile();
cache[key] = method;
}
return method.Invoke(input);
}