C# 有效地消除.NET表达式树中的常见子表达式

C# 有效地消除.NET表达式树中的常见子表达式,c#,.net,algorithm,optimization,expression-trees,C#,.net,Algorithm,Optimization,Expression Trees,我已经编写了一个DSL和一个编译器,可以从中生成一个.NET表达式树。 树中的所有表达式都没有副作用,并且表达式保证是“非语句”表达式(没有局部变量、循环、块等)。(编辑:树可能包括文字、属性访问、标准运算符和函数调用-这些操作可能在内部执行类似于记忆的奇特操作,但在外部没有副作用) 现在我想对它执行“公共子表达式消除”优化 例如,给定一棵对应于C#lambda的树: foo=>(foo.Bar*5+foo.Baz*2>7) ||(食物棒*5+食物棒*2 { var local1=foo.Bar

我已经编写了一个DSL和一个编译器,可以从中生成一个.NET表达式树。 树中的所有表达式都没有副作用,并且表达式保证是“非语句”表达式(没有局部变量、循环、块等)。(编辑:树可能包括文字、属性访问、标准运算符和函数调用-这些操作可能在内部执行类似于记忆的奇特操作,但在外部没有副作用)

现在我想对它执行“公共子表达式消除”优化

例如,给定一棵对应于C#lambda的树:

foo=>(foo.Bar*5+foo.Baz*2>7)
||(食物棒*5+食物棒*2<3)
||(foo.Bar*5+3==foo.Xyz)
…我想生成树等价物(忽略某些短路语义被忽略的事实):

foo=>
{
var local1=foo.Bar*5;
//注意,这个局部变量依赖于第一个局部变量。
var local2=local1+foo.Baz*2;
//请注意,没有生成不必要的局部变量。
返回local2>7 | | local2<3 | |(local1+3==foo.Xyz);
}
我对编写表达式访问者很熟悉,但这种优化的算法对我来说并不明显——我当然可以在树中找到“重复项”,但显然有一些技巧可以分析子树内和子树之间的依赖关系,从而高效、正确地消除子表达式


我在谷歌上寻找算法,但它们似乎很复杂,很难快速实现。此外,它们似乎非常“通用”,不一定考虑到我所拥有的树的简单性。

免责声明:我从未解决过这样的问题,我只是抛出了一个似乎相当有效的想法:

树中的每个节点都有某种签名。散列应该可以,冲突可以处理。签名必须将所有Foo.Bar条目映射到相同的值

遍历树(O(n))构建内部节点的签名列表(忽略叶子),在表达式大小的组合键上排序,然后签名(O(n log n))。取列表中最小表达式的最常用项(O(n))并用局部变量替换该表达式。(此时请检查它们是否真正匹配,以防发生哈希冲突。B)

重复这个步骤,直到你一事无成。这不可能运行超过n/2次,因此将整个操作限定为O(n^2 log n)

  • 制作一个可以比较任意
    表达式的
    SortedDictionary

    (您可以在此处定义自己的任意比较函数——例如,您可以按字典顺序比较表达式的类型,如果它们比较相等,则可以逐个比较子表达式。)

  • 把所有的叶子都翻一遍,加在字典里;如果它们已经存在,那么它们是重复的,所以合并它们。
    (如果这个叶是它的第一个实例,那么现在也是为它发出代码的好时机,比如创建一个新变量;然后,您可以将发出的代码存储在字典中的
    对象
    值中。)

  • 然后检查所有以前叶子的父母,并将他们添加到字典中;如果它们已经存在,那么它们是重复的,所以合并它们

  • 继续一层一层地往上爬,直到你到达树根


  • 现在,您知道了所有重复项是什么,它们发生在哪里,并为它们生成了代码。

    您正在做不必要的工作,常见子表达式消除是抖动优化器的工作。让我们以您的示例为例,看看生成的代码。我是这样写的:

        static void Main(string[] args) {
            var lambda = new Func<Foo, bool>(foo => 
                   (foo.Bar * 5 + foo.Baz * 2 > 7)
                || (foo.Bar * 5 + foo.Baz * 2 < 3) 
                || (foo.Bar * 5 + 3 == foo.Xyz));
            var obj = new Foo() { Bar = 1, Baz = 2, Xyz = 3 };
            var result = lambda(obj);
            Console.WriteLine(result);
        }
    }
    
    class Foo {
        public int Bar { get; internal set; }
        public int Baz { get; internal set; }
        public int Xyz { get; internal set; }
    }
    
    static void Main(字符串[]args){
    var lambda=新函数(foo=>
    (foo.Bar*5+foo.Baz*2>7)
    ||(食物棒*5+食物棒*2<3)
    ||(foo.Bar*5+3==foo.Xyz));
    var obj=newfoo(){Bar=1,Baz=2,Xyz=3};
    var结果=λ(obj);
    控制台写入线(结果);
    }
    }
    福班{
    公共整型条{get;内部集;}
    公共int Baz{get;内部集;}
    公共int Xyz{get;内部集;}
    }
    
    x86抖动为lambda表达式生成了以下机器代码:

    006526B8  push        ebp                          ; prologue
    006526B9  mov         ebp,esp  
    006526BB  push        esi  
    006526BC  mov         esi,dword ptr [ecx+4]        ; esi = foo.Bar
    006526BF  lea         esi,[esi+esi*4]              ; esi = 5 * foo.Bar
    006526C2  mov         edx,dword ptr [ecx+8]        ; edx = foo.Baz
    006526C5  add         edx,edx                      ; edx = 2 * foo.Baz
    006526C7  lea         eax,[esi+edx]                ; eax = 5 * foo.Bar + 2 * foo.Baz
    006526CA  cmp         eax,7                        ; > 7 test
    006526CD  jg          006526E7                     ; > 7 then return true
    006526CF  add         edx,esi                      ; HERE!!
    006526D1  cmp         edx,3                        ; < 3 test
    006526D4  jl          006526E7                     ; < 3 then return true
    006526D6  add         esi,3                        ; HERE!!
    006526D9  mov         eax,esi  
    006526DB  cmp         eax,dword ptr [ecx+0Ch]      ; == foo.Xyz test
    006526DE  sete        al                           ; convert to bool
    006526E1  movzx       eax,al  
    006526E4  pop         esi                          ; epilogue
    006526E5  pop         ebp  
    006526E6  ret 
    006526E7  mov         eax,1  
    006526EC  pop         esi  
    006526ED  pop         ebp  
    006526EE  ret   
    
    006526B8推式ebp;开场白
    006526B9 mov电子制动盘,esp
    006526BB推式esi
    006526BC电影esi,dword ptr[ecx+4];esi=foo.Bar
    006526BF lea esi,[esi+esi*4];esi=5*foo.Bar
    006526C2 mov edx,dword ptr[ecx+8];edx=foo.Baz
    006526C5增加edx,edx;edx=2*foo.Baz
    006526C7 lea eax,[esi+edx];eax=5*foo.Bar+2*foo.Baz
    006526CA cmp eax,7;>7试验
    006526CD jg 006526E7;>7然后返回true
    006526CF添加edx、esi;在这里
    006526D1 cmp edx,3;<3试验
    006526D4 jl 006526E7;<3然后返回true
    006526D6增加esi,3;在这里
    006526D9 mov eax,esi
    006526DB cmp eax,dword ptr[ecx+0Ch];==foo.Xyz测试
    006526DE sete al;转换成布尔
    006526E1 movzx eax,铝
    006526E4 pop esi;后记
    006526E5波普ebp
    006526E6 ret
    006526E7 mov eax,1
    006526EC pop esi
    006526ED pop ebp
    006526EE ret
    
    我在代码中用HERE标记了消除
    foo.Bar*5
    子表达式的位置。值得注意的是,它没有消除
    foo.Bar*5+foo.Baz*2
    子表达式,而是在地址006526CF处再次执行添加。
        static void Main(string[] args) {
            var lambda = new Func<Foo, bool>(foo => 
                   (foo.Bar * 5 + foo.Baz * 2 > 7)
                || (foo.Bar * 5 + foo.Baz * 2 < 3) 
                || (foo.Bar * 5 + 3 == foo.Xyz));
            var obj = new Foo() { Bar = 1, Baz = 2, Xyz = 3 };
            var result = lambda(obj);
            Console.WriteLine(result);
        }
    }
    
    class Foo {
        public int Bar { get; internal set; }
        public int Baz { get; internal set; }
        public int Xyz { get; internal set; }
    }
    
    006526B8  push        ebp                          ; prologue
    006526B9  mov         ebp,esp  
    006526BB  push        esi  
    006526BC  mov         esi,dword ptr [ecx+4]        ; esi = foo.Bar
    006526BF  lea         esi,[esi+esi*4]              ; esi = 5 * foo.Bar
    006526C2  mov         edx,dword ptr [ecx+8]        ; edx = foo.Baz
    006526C5  add         edx,edx                      ; edx = 2 * foo.Baz
    006526C7  lea         eax,[esi+edx]                ; eax = 5 * foo.Bar + 2 * foo.Baz
    006526CA  cmp         eax,7                        ; > 7 test
    006526CD  jg          006526E7                     ; > 7 then return true
    006526CF  add         edx,esi                      ; HERE!!
    006526D1  cmp         edx,3                        ; < 3 test
    006526D4  jl          006526E7                     ; < 3 then return true
    006526D6  add         esi,3                        ; HERE!!
    006526D9  mov         eax,esi  
    006526DB  cmp         eax,dword ptr [ecx+0Ch]      ; == foo.Xyz test
    006526DE  sete        al                           ; convert to bool
    006526E1  movzx       eax,al  
    006526E4  pop         esi                          ; epilogue
    006526E5  pop         ebp  
    006526E6  ret 
    006526E7  mov         eax,1  
    006526EC  pop         esi  
    006526ED  pop         ebp  
    006526EE  ret   
    
    Binary OR (27,1)
      lhs:
        Binary OR (19,1)
          lhs:
            Binary GREATER (9,1)
              lhs:
                Binary ADD (7,2)
                  lhs:
                    Binary MULTIPLY (3,2)
                      lhs:
                        Id 'Bar' (1,1)
                      rhs:
                        Number 5 (2,1)
                  rhs:
                    Binary MULTIPLY (6,1)
                      lhs:
                        Id 'Baz' (4,1)
                      rhs:
                        Number 2 (5,1)
              rhs:
                Number 7 (8,1)
          rhs:
            Binary LESS (18,1)
              lhs:
                ref to Binary ADD (7,2)
              rhs:
                Number 3 (17,2)
      rhs:
        Binary EQUALS (26,1)
          lhs:
            Binary ADD (24,1)
              lhs:
                ref to Binary MULTIPLY (3,2)
              rhs:
                ref to Number 3 (17,2)
          rhs:
            Id 'Xyz' (25,1)
    
    t3 = (Bar) * (5);
    t7 = (t3) + ((Baz) * (2));
    return (((t7) > (7)) || ((t7) < (3))) || (((t3) + (3)) == (Xyz));