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